src/java.base/share/classes/com/sun/java/util/jar/pack/PackageReader.java
changeset 47216 71c04702a3d5
parent 40792 cfcc66f92aec
child 55737 45133b603c21
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/java.base/share/classes/com/sun/java/util/jar/pack/PackageReader.java	Tue Sep 12 19:03:39 2017 +0200
@@ -0,0 +1,2376 @@
+/*
+ * Copyright (c) 2001, 2016, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package com.sun.java.util.jar.pack;
+
+import com.sun.java.util.jar.pack.ConstantPool.*;
+import com.sun.java.util.jar.pack.Package.Class;
+import com.sun.java.util.jar.pack.Package.File;
+import com.sun.java.util.jar.pack.Package.InnerClass;
+import java.io.ByteArrayOutputStream;
+import java.io.EOFException;
+import java.io.PrintStream;
+import java.io.FilterInputStream;
+import java.io.BufferedInputStream;
+import java.io.InputStream;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Map;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.HashSet;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.ListIterator;
+import java.util.Set;
+import static com.sun.java.util.jar.pack.Constants.*;
+
+/**
+ * Reader for a package file.
+ *
+ * @see PackageWriter
+ * @author John Rose
+ */
+class PackageReader extends BandStructure {
+    Package pkg;
+    byte[] bytes;
+    LimitedBuffer in;
+    Package.Version packageVersion;
+
+    PackageReader(Package pkg, InputStream in) throws IOException {
+        this.pkg = pkg;
+        this.in = new LimitedBuffer(in);
+    }
+
+    /** A buffered input stream which is careful not to
+     *  read its underlying stream ahead of a given mark,
+     *  called the 'readLimit'.  This property declares
+     *  the maximum number of characters that future reads
+     *  can consume from the underlying stream.
+     */
+    static
+    class LimitedBuffer extends BufferedInputStream {
+        long served;     // total number of charburgers served
+        int  servedPos;  // ...as of this value of super.pos
+        long limit;      // current declared limit
+        long buffered;
+        public boolean atLimit() {
+            boolean z = (getBytesServed() == limit);
+            assert(!z || limit == buffered);
+            return z;
+        }
+        public long getBytesServed() {
+            return served + (pos - servedPos);
+        }
+        public void setReadLimit(long newLimit) {
+            if (newLimit == -1)
+                limit = -1;
+            else
+                limit = getBytesServed() + newLimit;
+        }
+        public long getReadLimit() {
+            if (limit == -1)
+                return limit;
+            else
+                return limit - getBytesServed();
+        }
+        public int read() throws IOException {
+            if (pos < count) {
+                // fast path
+                return buf[pos++] & 0xFF;
+            }
+            served += (pos - servedPos);
+            int ch = super.read();
+            servedPos = pos;
+            if (ch >= 0)  served += 1;
+            assert(served <= limit || limit == -1);
+            return ch;
+        }
+        public int read(byte b[], int off, int len) throws IOException {
+            served += (pos - servedPos);
+            int nr = super.read(b, off, len);
+            servedPos = pos;
+            if (nr >= 0)  served += nr;
+            //assert(served <= limit || limit == -1);
+            return nr;
+        }
+        public long skip(long n) throws IOException {
+            throw new RuntimeException("no skipping");
+        }
+        LimitedBuffer(InputStream originalIn) {
+            super(null, 1<<14);
+            servedPos = pos;
+            super.in = new FilterInputStream(originalIn) {
+                public int read() throws IOException {
+                    if (buffered == limit)
+                        return -1;
+                    ++buffered;
+                    return super.read();
+                }
+                public int read(byte b[], int off, int len) throws IOException {
+                    if (buffered == limit)
+                        return -1;
+                    if (limit != -1) {
+                        long remaining = limit - buffered;
+                        if (len > remaining)
+                            len = (int)remaining;
+                    }
+                    int nr = super.read(b, off, len);
+                    if (nr >= 0)  buffered += nr;
+                    return nr;
+                }
+            };
+        }
+    }
+
+    void read() throws IOException {
+        boolean ok = false;
+        try {
+            //  pack200_archive:
+            //        file_header
+            //        *band_headers :BYTE1
+            //        cp_bands
+            //        attr_definition_bands
+            //        ic_bands
+            //        class_bands
+            //        bc_bands
+            //        file_bands
+            readFileHeader();
+            readBandHeaders();
+            readConstantPool();  // cp_bands
+            readAttrDefs();
+            readInnerClasses();
+            Class[] classes = readClasses();
+            readByteCodes();
+            readFiles();     // file_bands
+            assert(archiveSize1 == 0 || in.atLimit());
+            assert(archiveSize1 == 0 ||
+                   in.getBytesServed() == archiveSize0+archiveSize1);
+            all_bands.doneDisbursing();
+
+            // As a post-pass, build constant pools and inner classes.
+            for (int i = 0; i < classes.length; i++) {
+                reconstructClass(classes[i]);
+            }
+
+            ok = true;
+        } catch (Exception ee) {
+            Utils.log.warning("Error on input: "+ee, ee);
+            if (verbose > 0)
+                Utils.log.info("Stream offsets:"+
+                                 " served="+in.getBytesServed()+
+                                 " buffered="+in.buffered+
+                                 " limit="+in.limit);
+            //if (verbose > 0)  ee.printStackTrace();
+            if (ee instanceof IOException)  throw (IOException)ee;
+            if (ee instanceof RuntimeException)  throw (RuntimeException)ee;
+            throw new Error("error unpacking", ee);
+        }
+    }
+
+    // Temporary count values, until band decoding gets rolling.
+    int[] tagCount = new int[CONSTANT_Limit];
+    int numFiles;
+    int numAttrDefs;
+    int numInnerClasses;
+    int numClasses;
+
+    void readFileHeader() throws IOException {
+        //  file_header:
+        //        archive_magic archive_header
+        readArchiveMagic();
+        readArchiveHeader();
+    }
+
+    // Local routine used to parse fixed-format scalars
+    // in the file_header:
+    private int getMagicInt32() throws IOException {
+        int res = 0;
+        for (int i = 0; i < 4; i++) {
+            res <<= 8;
+            res |= (archive_magic.getByte() & 0xFF);
+        }
+        return res;
+    }
+
+    static final int MAGIC_BYTES = 4;
+
+    void readArchiveMagic() throws IOException {
+        // Read a minimum of bytes in the first gulp.
+        in.setReadLimit(MAGIC_BYTES + AH_LENGTH_MIN);
+
+        //  archive_magic:
+        //        #archive_magic_word :BYTE1[4]
+        archive_magic.expectLength(MAGIC_BYTES);
+        archive_magic.readFrom(in);
+
+        // read and check magic numbers:
+        int magic = getMagicInt32();
+        if (pkg.magic != magic) {
+            throw new IOException("Unexpected package magic number: got "
+                    + magic + "; expected " + pkg.magic);
+        }
+        archive_magic.doneDisbursing();
+    }
+
+     // Fixed 6211177, converted to throw IOException
+    void checkArchiveVersion() throws IOException {
+        Package.Version versionFound = null;
+        for (Package.Version v : new Package.Version[] {
+                JAVA8_PACKAGE_VERSION,
+                JAVA7_PACKAGE_VERSION,
+                JAVA6_PACKAGE_VERSION,
+                JAVA5_PACKAGE_VERSION
+            }) {
+            if (packageVersion.equals(v)) {
+                versionFound = v;
+                break;
+            }
+        }
+        if (versionFound == null) {
+            String expVer =   JAVA8_PACKAGE_VERSION.toString()
+                            + "OR"
+                            + JAVA7_PACKAGE_VERSION.toString()
+                            + " OR "
+                            + JAVA6_PACKAGE_VERSION.toString()
+                            + " OR "
+                            + JAVA5_PACKAGE_VERSION.toString();
+            throw new IOException("Unexpected package minor version: got "
+                    +  packageVersion.toString() + "; expected " + expVer);
+        }
+    }
+
+    void readArchiveHeader() throws IOException {
+        //  archive_header:
+        //        #archive_minver :UNSIGNED5[1]
+        //        #archive_majver :UNSIGNED5[1]
+        //        #archive_options :UNSIGNED5[1]
+        //        (archive_file_counts) ** (#have_file_headers)
+        //        (archive_special_counts) ** (#have_special_formats)
+        //        cp_counts
+        //        class_counts
+        //
+        //  archive_file_counts:
+        //        #archive_size_hi :UNSIGNED5[1]
+        //        #archive_size_lo :UNSIGNED5[1]
+        //        #archive_next_count :UNSIGNED5[1]
+        //        #archive_modtime :UNSIGNED5[1]
+        //        #file_count :UNSIGNED5[1]
+        //
+        //  class_counts:
+        //        #ic_count :UNSIGNED5[1]
+        //        #default_class_minver :UNSIGNED5[1]
+        //        #default_class_majver :UNSIGNED5[1]
+        //        #class_count :UNSIGNED5[1]
+        //
+        //  archive_special_counts:
+        //        #band_headers_size :UNSIGNED5[1]
+        //        #attr_definition_count :UNSIGNED5[1]
+        //
+        archive_header_0.expectLength(AH_LENGTH_0);
+        archive_header_0.readFrom(in);
+
+        int minver = archive_header_0.getInt();
+        int majver = archive_header_0.getInt();
+        packageVersion = Package.Version.of(majver, minver);
+        checkArchiveVersion();
+        this.initHighestClassVersion(JAVA7_MAX_CLASS_VERSION);
+
+        archiveOptions = archive_header_0.getInt();
+        archive_header_0.doneDisbursing();
+
+        // detect archive optional fields in archive header
+        boolean haveSpecial = testBit(archiveOptions, AO_HAVE_SPECIAL_FORMATS);
+        boolean haveFiles   = testBit(archiveOptions, AO_HAVE_FILE_HEADERS);
+        boolean haveNumbers = testBit(archiveOptions, AO_HAVE_CP_NUMBERS);
+        boolean haveCPExtra = testBit(archiveOptions, AO_HAVE_CP_EXTRAS);
+        initAttrIndexLimit();
+
+        // now we are ready to use the data:
+        archive_header_S.expectLength(haveFiles? AH_LENGTH_S: 0);
+        archive_header_S.readFrom(in);
+        if (haveFiles) {
+            long sizeHi = archive_header_S.getInt();
+            long sizeLo = archive_header_S.getInt();
+            archiveSize1 = (sizeHi << 32) + ((sizeLo << 32) >>> 32);
+            // Set the limit, now, up to the file_bits.
+            in.setReadLimit(archiveSize1);  // for debug only
+        } else {
+            archiveSize1 = 0;
+            in.setReadLimit(-1);  // remove limitation
+        }
+        archive_header_S.doneDisbursing();
+        archiveSize0 = in.getBytesServed();
+
+        int remainingHeaders = AH_LENGTH_MIN - AH_LENGTH_0 - AH_LENGTH_S;
+        if (haveFiles)    remainingHeaders += AH_FILE_HEADER_LEN;
+        if (haveSpecial)  remainingHeaders += AH_SPECIAL_FORMAT_LEN;
+        if (haveNumbers)  remainingHeaders += AH_CP_NUMBER_LEN;
+        if (haveCPExtra)  remainingHeaders += AH_CP_EXTRA_LEN;
+        archive_header_1.expectLength(remainingHeaders);
+        archive_header_1.readFrom(in);
+
+        if (haveFiles) {
+            archiveNextCount = archive_header_1.getInt();
+            pkg.default_modtime = archive_header_1.getInt();
+            numFiles = archive_header_1.getInt();
+        } else {
+            archiveNextCount = 0;
+            numFiles = 0;
+        }
+
+        if (haveSpecial) {
+            band_headers.expectLength(archive_header_1.getInt());
+            numAttrDefs = archive_header_1.getInt();
+        } else {
+            band_headers.expectLength(0);
+            numAttrDefs = 0;
+        }
+
+        readConstantPoolCounts(haveNumbers, haveCPExtra);
+
+        numInnerClasses = archive_header_1.getInt();
+
+        minver = (short) archive_header_1.getInt();
+        majver = (short) archive_header_1.getInt();
+        pkg.defaultClassVersion = Package.Version.of(majver, minver);
+        numClasses = archive_header_1.getInt();
+
+        archive_header_1.doneDisbursing();
+
+        // set some derived archive bits
+        if (testBit(archiveOptions, AO_DEFLATE_HINT)) {
+            pkg.default_options |= FO_DEFLATE_HINT;
+        }
+    }
+
+    void readBandHeaders() throws IOException {
+        band_headers.readFrom(in);
+        bandHeaderBytePos = 1;  // Leave room to pushback the initial XB byte.
+        bandHeaderBytes = new byte[bandHeaderBytePos + band_headers.length()];
+        for (int i = bandHeaderBytePos; i < bandHeaderBytes.length; i++) {
+            bandHeaderBytes[i] = (byte) band_headers.getByte();
+        }
+        band_headers.doneDisbursing();
+    }
+
+    void readConstantPoolCounts(boolean haveNumbers, boolean haveCPExtra) throws IOException {
+        // size the constant pools:
+        for (int k = 0; k < ConstantPool.TAGS_IN_ORDER.length; k++) {
+            //  cp_counts:
+            //        #cp_Utf8_count :UNSIGNED5[1]
+            //        (cp_number_counts) ** (#have_cp_numbers)
+            //        #cp_String_count :UNSIGNED5[1]
+            //        #cp_Class_count :UNSIGNED5[1]
+            //        #cp_Signature_count :UNSIGNED5[1]
+            //        #cp_Descr_count :UNSIGNED5[1]
+            //        #cp_Field_count :UNSIGNED5[1]
+            //        #cp_Method_count :UNSIGNED5[1]
+            //        #cp_Imethod_count :UNSIGNED5[1]
+            //        (cp_attr_counts) ** (#have_cp_attr_counts)
+            //
+            //  cp_number_counts:
+            //        #cp_Int_count :UNSIGNED5[1]
+            //        #cp_Float_count :UNSIGNED5[1]
+            //        #cp_Long_count :UNSIGNED5[1]
+            //        #cp_Double_count :UNSIGNED5[1]
+            //
+            //  cp_extra_counts:
+            //        #cp_MethodHandle_count :UNSIGNED5[1]
+            //        #cp_MethodType_count :UNSIGNED5[1]
+            //        #cp_InvokeDynamic_count :UNSIGNED5[1]
+            //        #cp_BootstrapMethod_count :UNSIGNED5[1]
+            //
+            byte tag = ConstantPool.TAGS_IN_ORDER[k];
+            if (!haveNumbers) {
+                // These four counts are optional.
+                switch (tag) {
+                case CONSTANT_Integer:
+                case CONSTANT_Float:
+                case CONSTANT_Long:
+                case CONSTANT_Double:
+                    continue;
+                }
+            }
+            if (!haveCPExtra) {
+                // These four counts are optional.
+                switch (tag) {
+                case CONSTANT_MethodHandle:
+                case CONSTANT_MethodType:
+                case CONSTANT_InvokeDynamic:
+                case CONSTANT_BootstrapMethod:
+                    continue;
+                }
+            }
+            tagCount[tag] = archive_header_1.getInt();
+        }
+    }
+
+    protected Index getCPIndex(byte tag) {
+        return pkg.cp.getIndexByTag(tag);
+    }
+    Index initCPIndex(byte tag, Entry[] cpMap) {
+        if (verbose > 3) {
+            for (int i = 0; i < cpMap.length; i++) {
+                Utils.log.fine("cp.add "+cpMap[i]);
+            }
+        }
+        Index index = ConstantPool.makeIndex(ConstantPool.tagName(tag), cpMap);
+        if (verbose > 1)  Utils.log.fine("Read "+index);
+        pkg.cp.initIndexByTag(tag, index);
+        return index;
+    }
+
+    void checkLegacy(String bandname) {
+        if (packageVersion.lessThan(JAVA7_PACKAGE_VERSION)) {
+            throw new RuntimeException("unexpected band " + bandname);
+        }
+    }
+    void readConstantPool() throws IOException {
+        //  cp_bands:
+        //        cp_Utf8
+        //        *cp_Int :UDELTA5
+        //        *cp_Float :UDELTA5
+        //        cp_Long
+        //        cp_Double
+        //        *cp_String :UDELTA5  (cp_Utf8)
+        //        *cp_Class :UDELTA5  (cp_Utf8)
+        //        cp_Signature
+        //        cp_Descr
+        //        cp_Field
+        //        cp_Method
+        //        cp_Imethod
+
+        if (verbose > 0)  Utils.log.info("Reading CP");
+
+        for (int k = 0; k < ConstantPool.TAGS_IN_ORDER.length; k++) {
+            byte tag = ConstantPool.TAGS_IN_ORDER[k];
+            int  len = tagCount[tag];
+
+            Entry[] cpMap = new Entry[len];
+            if (verbose > 0)
+                Utils.log.info("Reading "+cpMap.length+" "+ConstantPool.tagName(tag)+" entries...");
+
+            switch (tag) {
+            case CONSTANT_Utf8:
+                readUtf8Bands(cpMap);
+                break;
+            case CONSTANT_Integer:
+                cp_Int.expectLength(cpMap.length);
+                cp_Int.readFrom(in);
+                for (int i = 0; i < cpMap.length; i++) {
+                    int x = cp_Int.getInt();  // coding handles signs OK
+                    cpMap[i] = ConstantPool.getLiteralEntry(x);
+                }
+                cp_Int.doneDisbursing();
+                break;
+            case CONSTANT_Float:
+                cp_Float.expectLength(cpMap.length);
+                cp_Float.readFrom(in);
+                for (int i = 0; i < cpMap.length; i++) {
+                    int x = cp_Float.getInt();
+                    float fx = Float.intBitsToFloat(x);
+                    cpMap[i] = ConstantPool.getLiteralEntry(fx);
+                }
+                cp_Float.doneDisbursing();
+                break;
+            case CONSTANT_Long:
+                //  cp_Long:
+                //        *cp_Long_hi :UDELTA5
+                //        *cp_Long_lo :DELTA5
+                cp_Long_hi.expectLength(cpMap.length);
+                cp_Long_hi.readFrom(in);
+                cp_Long_lo.expectLength(cpMap.length);
+                cp_Long_lo.readFrom(in);
+                for (int i = 0; i < cpMap.length; i++) {
+                    long hi = cp_Long_hi.getInt();
+                    long lo = cp_Long_lo.getInt();
+                    long x = (hi << 32) + ((lo << 32) >>> 32);
+                    cpMap[i] = ConstantPool.getLiteralEntry(x);
+                }
+                cp_Long_hi.doneDisbursing();
+                cp_Long_lo.doneDisbursing();
+                break;
+            case CONSTANT_Double:
+                //  cp_Double:
+                //        *cp_Double_hi :UDELTA5
+                //        *cp_Double_lo :DELTA5
+                cp_Double_hi.expectLength(cpMap.length);
+                cp_Double_hi.readFrom(in);
+                cp_Double_lo.expectLength(cpMap.length);
+                cp_Double_lo.readFrom(in);
+                for (int i = 0; i < cpMap.length; i++) {
+                    long hi = cp_Double_hi.getInt();
+                    long lo = cp_Double_lo.getInt();
+                    long x = (hi << 32) + ((lo << 32) >>> 32);
+                    double dx = Double.longBitsToDouble(x);
+                    cpMap[i] = ConstantPool.getLiteralEntry(dx);
+                }
+                cp_Double_hi.doneDisbursing();
+                cp_Double_lo.doneDisbursing();
+                break;
+            case CONSTANT_String:
+                cp_String.expectLength(cpMap.length);
+                cp_String.readFrom(in);
+                cp_String.setIndex(getCPIndex(CONSTANT_Utf8));
+                for (int i = 0; i < cpMap.length; i++) {
+                    cpMap[i] = ConstantPool.getLiteralEntry(cp_String.getRef().stringValue());
+                }
+                cp_String.doneDisbursing();
+                break;
+            case CONSTANT_Class:
+                cp_Class.expectLength(cpMap.length);
+                cp_Class.readFrom(in);
+                cp_Class.setIndex(getCPIndex(CONSTANT_Utf8));
+                for (int i = 0; i < cpMap.length; i++) {
+                    cpMap[i] = ConstantPool.getClassEntry(cp_Class.getRef().stringValue());
+                }
+                cp_Class.doneDisbursing();
+                break;
+            case CONSTANT_Signature:
+                readSignatureBands(cpMap);
+                break;
+            case CONSTANT_NameandType:
+                //  cp_Descr:
+                //        *cp_Descr_type :DELTA5  (cp_Signature)
+                //        *cp_Descr_name :UDELTA5  (cp_Utf8)
+                cp_Descr_name.expectLength(cpMap.length);
+                cp_Descr_name.readFrom(in);
+                cp_Descr_name.setIndex(getCPIndex(CONSTANT_Utf8));
+                cp_Descr_type.expectLength(cpMap.length);
+                cp_Descr_type.readFrom(in);
+                cp_Descr_type.setIndex(getCPIndex(CONSTANT_Signature));
+                for (int i = 0; i < cpMap.length; i++) {
+                    Entry ref  = cp_Descr_name.getRef();
+                    Entry ref2 = cp_Descr_type.getRef();
+                    cpMap[i] = ConstantPool.getDescriptorEntry((Utf8Entry)ref,
+                                                        (SignatureEntry)ref2);
+                }
+                cp_Descr_name.doneDisbursing();
+                cp_Descr_type.doneDisbursing();
+                break;
+            case CONSTANT_Fieldref:
+                readMemberRefs(tag, cpMap, cp_Field_class, cp_Field_desc);
+                break;
+            case CONSTANT_Methodref:
+                readMemberRefs(tag, cpMap, cp_Method_class, cp_Method_desc);
+                break;
+            case CONSTANT_InterfaceMethodref:
+                readMemberRefs(tag, cpMap, cp_Imethod_class, cp_Imethod_desc);
+                break;
+            case CONSTANT_MethodHandle:
+                if (cpMap.length > 0) {
+                    checkLegacy(cp_MethodHandle_refkind.name());
+                }
+                cp_MethodHandle_refkind.expectLength(cpMap.length);
+                cp_MethodHandle_refkind.readFrom(in);
+                cp_MethodHandle_member.expectLength(cpMap.length);
+                cp_MethodHandle_member.readFrom(in);
+                cp_MethodHandle_member.setIndex(getCPIndex(CONSTANT_AnyMember));
+                for (int i = 0; i < cpMap.length; i++) {
+                    byte        refKind = (byte)        cp_MethodHandle_refkind.getInt();
+                    MemberEntry memRef  = (MemberEntry) cp_MethodHandle_member.getRef();
+                    cpMap[i] = ConstantPool.getMethodHandleEntry(refKind, memRef);
+                }
+                cp_MethodHandle_refkind.doneDisbursing();
+                cp_MethodHandle_member.doneDisbursing();
+                break;
+            case CONSTANT_MethodType:
+                if (cpMap.length > 0) {
+                    checkLegacy(cp_MethodType.name());
+                }
+                cp_MethodType.expectLength(cpMap.length);
+                cp_MethodType.readFrom(in);
+                cp_MethodType.setIndex(getCPIndex(CONSTANT_Signature));
+                for (int i = 0; i < cpMap.length; i++) {
+                    SignatureEntry typeRef  = (SignatureEntry) cp_MethodType.getRef();
+                    cpMap[i] = ConstantPool.getMethodTypeEntry(typeRef);
+                }
+                cp_MethodType.doneDisbursing();
+                break;
+            case CONSTANT_InvokeDynamic:
+                if (cpMap.length > 0) {
+                    checkLegacy(cp_InvokeDynamic_spec.name());
+                }
+                cp_InvokeDynamic_spec.expectLength(cpMap.length);
+                cp_InvokeDynamic_spec.readFrom(in);
+                cp_InvokeDynamic_spec.setIndex(getCPIndex(CONSTANT_BootstrapMethod));
+                cp_InvokeDynamic_desc.expectLength(cpMap.length);
+                cp_InvokeDynamic_desc.readFrom(in);
+                cp_InvokeDynamic_desc.setIndex(getCPIndex(CONSTANT_NameandType));
+                for (int i = 0; i < cpMap.length; i++) {
+                    BootstrapMethodEntry bss   = (BootstrapMethodEntry) cp_InvokeDynamic_spec.getRef();
+                    DescriptorEntry      descr = (DescriptorEntry)      cp_InvokeDynamic_desc.getRef();
+                    cpMap[i] = ConstantPool.getInvokeDynamicEntry(bss, descr);
+                }
+                cp_InvokeDynamic_spec.doneDisbursing();
+                cp_InvokeDynamic_desc.doneDisbursing();
+                break;
+            case CONSTANT_BootstrapMethod:
+                if (cpMap.length > 0) {
+                    checkLegacy(cp_BootstrapMethod_ref.name());
+                }
+                cp_BootstrapMethod_ref.expectLength(cpMap.length);
+                cp_BootstrapMethod_ref.readFrom(in);
+                cp_BootstrapMethod_ref.setIndex(getCPIndex(CONSTANT_MethodHandle));
+                cp_BootstrapMethod_arg_count.expectLength(cpMap.length);
+                cp_BootstrapMethod_arg_count.readFrom(in);
+                int totalArgCount = cp_BootstrapMethod_arg_count.getIntTotal();
+                cp_BootstrapMethod_arg.expectLength(totalArgCount);
+                cp_BootstrapMethod_arg.readFrom(in);
+                cp_BootstrapMethod_arg.setIndex(getCPIndex(CONSTANT_LoadableValue));
+                for (int i = 0; i < cpMap.length; i++) {
+                    MethodHandleEntry bsm = (MethodHandleEntry) cp_BootstrapMethod_ref.getRef();
+                    int argc = cp_BootstrapMethod_arg_count.getInt();
+                    Entry[] argRefs = new Entry[argc];
+                    for (int j = 0; j < argc; j++) {
+                        argRefs[j] = cp_BootstrapMethod_arg.getRef();
+                    }
+                    cpMap[i] = ConstantPool.getBootstrapMethodEntry(bsm, argRefs);
+                }
+                cp_BootstrapMethod_ref.doneDisbursing();
+                cp_BootstrapMethod_arg_count.doneDisbursing();
+                cp_BootstrapMethod_arg.doneDisbursing();
+                break;
+            default:
+                throw new AssertionError("unexpected CP tag in package");
+            }
+
+            Index index = initCPIndex(tag, cpMap);
+
+            if (optDumpBands) {
+                try (PrintStream ps = new PrintStream(getDumpStream(index, ".idx"))) {
+                    printArrayTo(ps, index.cpMap, 0, index.cpMap.length);
+                }
+            }
+        }
+
+        cp_bands.doneDisbursing();
+
+        if (optDumpBands || verbose > 1) {
+            for (byte tag = CONSTANT_GroupFirst; tag < CONSTANT_GroupLimit; tag++) {
+                Index index = pkg.cp.getIndexByTag(tag);
+                if (index == null || index.isEmpty())  continue;
+                Entry[] cpMap = index.cpMap;
+                if (verbose > 1)
+                    Utils.log.info("Index group "+ConstantPool.tagName(tag)+" contains "+cpMap.length+" entries.");
+                if (optDumpBands) {
+                    try (PrintStream ps = new PrintStream(getDumpStream(index.debugName, tag, ".gidx", index))) {
+                        printArrayTo(ps, cpMap, 0, cpMap.length, true);
+                    }
+                }
+            }
+        }
+
+        setBandIndexes();
+    }
+
+    void readUtf8Bands(Entry[] cpMap) throws IOException {
+        //  cp_Utf8:
+        //        *cp_Utf8_prefix :DELTA5
+        //        *cp_Utf8_suffix :UNSIGNED5
+        //        *cp_Utf8_chars :CHAR3
+        //        *cp_Utf8_big_suffix :DELTA5
+        //        (*cp_Utf8_big_chars :DELTA5)
+        //          ** length(cp_Utf8_big_suffix)
+        int len = cpMap.length;
+        if (len == 0)
+            return;  // nothing to read
+
+        // Bands have implicit leading zeroes, for the empty string:
+        final int SUFFIX_SKIP_1 = 1;
+        final int PREFIX_SKIP_2 = 2;
+
+        // First band:  Read lengths of shared prefixes.
+        cp_Utf8_prefix.expectLength(Math.max(0, len - PREFIX_SKIP_2));
+        cp_Utf8_prefix.readFrom(in);
+
+        // Second band:  Read lengths of unshared suffixes:
+        cp_Utf8_suffix.expectLength(Math.max(0, len - SUFFIX_SKIP_1));
+        cp_Utf8_suffix.readFrom(in);
+
+        char[][] suffixChars = new char[len][];
+        int bigSuffixCount = 0;
+
+        // Third band:  Read the char values in the unshared suffixes:
+        cp_Utf8_chars.expectLength(cp_Utf8_suffix.getIntTotal());
+        cp_Utf8_chars.readFrom(in);
+        for (int i = 0; i < len; i++) {
+            int suffix = (i < SUFFIX_SKIP_1)? 0: cp_Utf8_suffix.getInt();
+            if (suffix == 0 && i >= SUFFIX_SKIP_1) {
+                // chars are packed in cp_Utf8_big_chars
+                bigSuffixCount += 1;
+                continue;
+            }
+            suffixChars[i] = new char[suffix];
+            for (int j = 0; j < suffix; j++) {
+                int ch = cp_Utf8_chars.getInt();
+                assert(ch == (char)ch);
+                suffixChars[i][j] = (char)ch;
+            }
+        }
+        cp_Utf8_chars.doneDisbursing();
+
+        // Fourth band:  Go back and size the specially packed strings.
+        int maxChars = 0;
+        cp_Utf8_big_suffix.expectLength(bigSuffixCount);
+        cp_Utf8_big_suffix.readFrom(in);
+        cp_Utf8_suffix.resetForSecondPass();
+        for (int i = 0; i < len; i++) {
+            int suffix = (i < SUFFIX_SKIP_1)? 0: cp_Utf8_suffix.getInt();
+            int prefix = (i < PREFIX_SKIP_2)? 0: cp_Utf8_prefix.getInt();
+            if (suffix == 0 && i >= SUFFIX_SKIP_1) {
+                assert(suffixChars[i] == null);
+                suffix = cp_Utf8_big_suffix.getInt();
+            } else {
+                assert(suffixChars[i] != null);
+            }
+            if (maxChars < prefix + suffix)
+                maxChars = prefix + suffix;
+        }
+        char[] buf = new char[maxChars];
+
+        // Fifth band(s):  Get the specially packed characters.
+        cp_Utf8_suffix.resetForSecondPass();
+        cp_Utf8_big_suffix.resetForSecondPass();
+        for (int i = 0; i < len; i++) {
+            if (i < SUFFIX_SKIP_1)  continue;
+            int suffix = cp_Utf8_suffix.getInt();
+            if (suffix != 0)  continue;  // already input
+            suffix = cp_Utf8_big_suffix.getInt();
+            suffixChars[i] = new char[suffix];
+            if (suffix == 0) {
+                // Do not bother to add an empty "(Utf8_big_0)" band.
+                continue;
+            }
+            IntBand packed = cp_Utf8_big_chars.newIntBand("(Utf8_big_"+i+")");
+            packed.expectLength(suffix);
+            packed.readFrom(in);
+            for (int j = 0; j < suffix; j++) {
+                int ch = packed.getInt();
+                assert(ch == (char)ch);
+                suffixChars[i][j] = (char)ch;
+            }
+            packed.doneDisbursing();
+        }
+        cp_Utf8_big_chars.doneDisbursing();
+
+        // Finally, sew together all the prefixes and suffixes.
+        cp_Utf8_prefix.resetForSecondPass();
+        cp_Utf8_suffix.resetForSecondPass();
+        cp_Utf8_big_suffix.resetForSecondPass();
+        for (int i = 0; i < len; i++) {
+            int prefix = (i < PREFIX_SKIP_2)? 0: cp_Utf8_prefix.getInt();
+            int suffix = (i < SUFFIX_SKIP_1)? 0: cp_Utf8_suffix.getInt();
+            if (suffix == 0 && i >= SUFFIX_SKIP_1)
+                suffix = cp_Utf8_big_suffix.getInt();
+
+            // by induction, the buffer is already filled with the prefix
+            System.arraycopy(suffixChars[i], 0, buf, prefix, suffix);
+
+            cpMap[i] = ConstantPool.getUtf8Entry(new String(buf, 0, prefix+suffix));
+        }
+
+        cp_Utf8_prefix.doneDisbursing();
+        cp_Utf8_suffix.doneDisbursing();
+        cp_Utf8_big_suffix.doneDisbursing();
+    }
+
+    Map<Utf8Entry, SignatureEntry> utf8Signatures;
+
+    void readSignatureBands(Entry[] cpMap) throws IOException {
+        //  cp_Signature:
+        //        *cp_Signature_form :DELTA5  (cp_Utf8)
+        //        *cp_Signature_classes :UDELTA5  (cp_Class)
+        cp_Signature_form.expectLength(cpMap.length);
+        cp_Signature_form.readFrom(in);
+        cp_Signature_form.setIndex(getCPIndex(CONSTANT_Utf8));
+        int[] numSigClasses = new int[cpMap.length];
+        for (int i = 0; i < cpMap.length; i++) {
+            Utf8Entry formRef = (Utf8Entry) cp_Signature_form.getRef();
+            numSigClasses[i] = ConstantPool.countClassParts(formRef);
+        }
+        cp_Signature_form.resetForSecondPass();
+        cp_Signature_classes.expectLength(getIntTotal(numSigClasses));
+        cp_Signature_classes.readFrom(in);
+        cp_Signature_classes.setIndex(getCPIndex(CONSTANT_Class));
+        utf8Signatures = new HashMap<>();
+        for (int i = 0; i < cpMap.length; i++) {
+            Utf8Entry formRef = (Utf8Entry) cp_Signature_form.getRef();
+            ClassEntry[] classRefs = new ClassEntry[numSigClasses[i]];
+            for (int j = 0; j < classRefs.length; j++) {
+                classRefs[j] = (ClassEntry) cp_Signature_classes.getRef();
+            }
+            SignatureEntry se = ConstantPool.getSignatureEntry(formRef, classRefs);
+            cpMap[i] = se;
+            utf8Signatures.put(se.asUtf8Entry(), se);
+        }
+        cp_Signature_form.doneDisbursing();
+        cp_Signature_classes.doneDisbursing();
+    }
+
+    void readMemberRefs(byte tag, Entry[] cpMap, CPRefBand cp_class, CPRefBand cp_desc) throws IOException {
+        //  cp_Field:
+        //        *cp_Field_class :DELTA5  (cp_Class)
+        //        *cp_Field_desc :UDELTA5  (cp_Descr)
+        //  cp_Method:
+        //        *cp_Method_class :DELTA5  (cp_Class)
+        //        *cp_Method_desc :UDELTA5  (cp_Descr)
+        //  cp_Imethod:
+        //        *cp_Imethod_class :DELTA5  (cp_Class)
+        //        *cp_Imethod_desc :UDELTA5  (cp_Descr)
+        cp_class.expectLength(cpMap.length);
+        cp_class.readFrom(in);
+        cp_class.setIndex(getCPIndex(CONSTANT_Class));
+        cp_desc.expectLength(cpMap.length);
+        cp_desc.readFrom(in);
+        cp_desc.setIndex(getCPIndex(CONSTANT_NameandType));
+        for (int i = 0; i < cpMap.length; i++) {
+            ClassEntry      mclass = (ClassEntry     ) cp_class.getRef();
+            DescriptorEntry mdescr = (DescriptorEntry) cp_desc.getRef();
+            cpMap[i] = ConstantPool.getMemberEntry(tag, mclass, mdescr);
+        }
+        cp_class.doneDisbursing();
+        cp_desc.doneDisbursing();
+    }
+
+    void readFiles() throws IOException {
+        //  file_bands:
+        //        *file_name :UNSIGNED5  (cp_Utf8)
+        //        *file_size_hi :UNSIGNED5
+        //        *file_size_lo :UNSIGNED5
+        //        *file_modtime :DELTA5
+        //        *file_options :UNSIGNED5
+        //        *file_bits :BYTE1
+        if (verbose > 0)
+            Utils.log.info("  ...building "+numFiles+" files...");
+        file_name.expectLength(numFiles);
+        file_size_lo.expectLength(numFiles);
+        int options = archiveOptions;
+        boolean haveSizeHi  = testBit(options, AO_HAVE_FILE_SIZE_HI);
+        boolean haveModtime = testBit(options, AO_HAVE_FILE_MODTIME);
+        boolean haveOptions = testBit(options, AO_HAVE_FILE_OPTIONS);
+        if (haveSizeHi)
+            file_size_hi.expectLength(numFiles);
+        if (haveModtime)
+            file_modtime.expectLength(numFiles);
+        if (haveOptions)
+            file_options.expectLength(numFiles);
+
+        file_name.readFrom(in);
+        file_size_hi.readFrom(in);
+        file_size_lo.readFrom(in);
+        file_modtime.readFrom(in);
+        file_options.readFrom(in);
+        file_bits.setInputStreamFrom(in);
+
+        Iterator<Class> nextClass = pkg.getClasses().iterator();
+
+        // Compute file lengths before reading any file bits.
+        long totalFileLength = 0;
+        long[] fileLengths = new long[numFiles];
+        for (int i = 0; i < numFiles; i++) {
+            long size = ((long)file_size_lo.getInt() << 32) >>> 32;
+            if (haveSizeHi)
+                size += (long)file_size_hi.getInt() << 32;
+            fileLengths[i] = size;
+            totalFileLength += size;
+        }
+        assert(in.getReadLimit() == -1 || in.getReadLimit() == totalFileLength);
+
+        byte[] buf = new byte[1<<16];
+        for (int i = 0; i < numFiles; i++) {
+            // %%% Use a big temp file for file bits?
+            Utf8Entry name = (Utf8Entry) file_name.getRef();
+            long size = fileLengths[i];
+            File file = pkg.new File(name);
+            file.modtime = pkg.default_modtime;
+            file.options = pkg.default_options;
+            if (haveModtime)
+                file.modtime += file_modtime.getInt();
+            if (haveOptions)
+                file.options |= file_options.getInt();
+            if (verbose > 1)
+                Utils.log.fine("Reading "+size+" bytes of "+name.stringValue());
+            long toRead = size;
+            while (toRead > 0) {
+                int nr = buf.length;
+                if (nr > toRead)  nr = (int) toRead;
+                nr = file_bits.getInputStream().read(buf, 0, nr);
+                if (nr < 0)  throw new EOFException();
+                file.addBytes(buf, 0, nr);
+                toRead -= nr;
+            }
+            pkg.addFile(file);
+            if (file.isClassStub()) {
+                assert(file.getFileLength() == 0);
+                Class cls = nextClass.next();
+                cls.initFile(file);
+            }
+        }
+
+        // Do the rest of the classes.
+        while (nextClass.hasNext()) {
+            Class cls = nextClass.next();
+            cls.initFile(null);  // implicitly initialize to a trivial one
+            cls.file.modtime = pkg.default_modtime;
+        }
+
+        file_name.doneDisbursing();
+        file_size_hi.doneDisbursing();
+        file_size_lo.doneDisbursing();
+        file_modtime.doneDisbursing();
+        file_options.doneDisbursing();
+        file_bits.doneDisbursing();
+        file_bands.doneDisbursing();
+
+        if (archiveSize1 != 0 && !in.atLimit()) {
+            throw new RuntimeException("Predicted archive_size "+
+                                       archiveSize1+" != "+
+                                       (in.getBytesServed()-archiveSize0));
+        }
+    }
+
+    void readAttrDefs() throws IOException {
+        //  attr_definition_bands:
+        //        *attr_definition_headers :BYTE1
+        //        *attr_definition_name :UNSIGNED5  (cp_Utf8)
+        //        *attr_definition_layout :UNSIGNED5  (cp_Utf8)
+        attr_definition_headers.expectLength(numAttrDefs);
+        attr_definition_name.expectLength(numAttrDefs);
+        attr_definition_layout.expectLength(numAttrDefs);
+        attr_definition_headers.readFrom(in);
+        attr_definition_name.readFrom(in);
+        attr_definition_layout.readFrom(in);
+        try (PrintStream dump = !optDumpBands ? null
+                 : new PrintStream(getDumpStream(attr_definition_headers, ".def")))
+        {
+            for (int i = 0; i < numAttrDefs; i++) {
+                int       header = attr_definition_headers.getByte();
+                Utf8Entry name   = (Utf8Entry) attr_definition_name.getRef();
+                Utf8Entry layout = (Utf8Entry) attr_definition_layout.getRef();
+                int       ctype  = (header &  ADH_CONTEXT_MASK);
+                int       index  = (header >> ADH_BIT_SHIFT) - ADH_BIT_IS_LSB;
+                Attribute.Layout def = new Attribute.Layout(ctype,
+                                                            name.stringValue(),
+                                                            layout.stringValue());
+                // Check layout string for Java 6 extensions.
+                String pvLayout = def.layoutForClassVersion(getHighestClassVersion());
+                if (!pvLayout.equals(def.layout())) {
+                    throw new IOException("Bad attribute layout in archive: "+def.layout());
+                }
+                this.setAttributeLayoutIndex(def, index);
+                if (dump != null)  dump.println(index+" "+def);
+            }
+        }
+        attr_definition_headers.doneDisbursing();
+        attr_definition_name.doneDisbursing();
+        attr_definition_layout.doneDisbursing();
+        // Attribute layouts define bands, one per layout element.
+        // Create them now, all at once.
+        makeNewAttributeBands();
+        attr_definition_bands.doneDisbursing();
+    }
+
+    void readInnerClasses() throws IOException {
+        //  ic_bands:
+        //        *ic_this_class :UDELTA5  (cp_Class)
+        //        *ic_flags :UNSIGNED5
+        //        *ic_outer_class :DELTA5  (null or cp_Class)
+        //        *ic_name :DELTA5  (null or cp_Utf8)
+        ic_this_class.expectLength(numInnerClasses);
+        ic_this_class.readFrom(in);
+        ic_flags.expectLength(numInnerClasses);
+        ic_flags.readFrom(in);
+        int longICCount = 0;
+        for (int i = 0; i < numInnerClasses; i++) {
+            int flags = ic_flags.getInt();
+            boolean longForm = (flags & ACC_IC_LONG_FORM) != 0;
+            if (longForm) {
+                longICCount += 1;
+            }
+        }
+        ic_outer_class.expectLength(longICCount);
+        ic_outer_class.readFrom(in);
+        ic_name.expectLength(longICCount);
+        ic_name.readFrom(in);
+        ic_flags.resetForSecondPass();
+        List<InnerClass> icList = new ArrayList<>(numInnerClasses);
+        for (int i = 0; i < numInnerClasses; i++) {
+            int flags = ic_flags.getInt();
+            boolean longForm = (flags & ACC_IC_LONG_FORM) != 0;
+            flags &= ~ACC_IC_LONG_FORM;
+            ClassEntry thisClass = (ClassEntry) ic_this_class.getRef();
+            ClassEntry outerClass;
+            Utf8Entry  thisName;
+            if (longForm) {
+                outerClass = (ClassEntry) ic_outer_class.getRef();
+                thisName   = (Utf8Entry)  ic_name.getRef();
+            } else {
+                String n = thisClass.stringValue();
+                String[] parse = Package.parseInnerClassName(n);
+                assert(parse != null);
+                String pkgOuter = parse[0];
+                //String number = parse[1];
+                String name     = parse[2];
+                if (pkgOuter == null)
+                    outerClass = null;
+                else
+                    outerClass = ConstantPool.getClassEntry(pkgOuter);
+                if (name == null)
+                    thisName   = null;
+                else
+                    thisName   = ConstantPool.getUtf8Entry(name);
+            }
+            InnerClass ic =
+                new InnerClass(thisClass, outerClass, thisName, flags);
+            assert(longForm || ic.predictable);
+            icList.add(ic);
+        }
+        ic_flags.doneDisbursing();
+        ic_this_class.doneDisbursing();
+        ic_outer_class.doneDisbursing();
+        ic_name.doneDisbursing();
+        pkg.setAllInnerClasses(icList);
+        ic_bands.doneDisbursing();
+    }
+
+    void readLocalInnerClasses(Class cls) throws IOException {
+        int nc = class_InnerClasses_N.getInt();
+        List<InnerClass> localICs = new ArrayList<>(nc);
+        for (int i = 0; i < nc; i++) {
+            ClassEntry thisClass = (ClassEntry) class_InnerClasses_RC.getRef();
+            int        flags     =              class_InnerClasses_F.getInt();
+            if (flags == 0) {
+                // A zero flag means copy a global IC here.
+                InnerClass ic = pkg.getGlobalInnerClass(thisClass);
+                assert(ic != null);  // must be a valid global IC reference
+                localICs.add(ic);
+            } else {
+                if (flags == ACC_IC_LONG_FORM)
+                    flags = 0;  // clear the marker bit
+                ClassEntry outer = (ClassEntry) class_InnerClasses_outer_RCN.getRef();
+                Utf8Entry name   = (Utf8Entry)  class_InnerClasses_name_RUN.getRef();
+                localICs.add(new InnerClass(thisClass, outer, name, flags));
+            }
+        }
+        cls.setInnerClasses(localICs);
+        // cls.expandLocalICs may add more tuples to ics also,
+        // or may even delete tuples.
+        // We cannot do that now, because we do not know the
+        // full contents of the local constant pool yet.
+    }
+
+    static final int NO_FLAGS_YET = 0;  // placeholder for later flag read-in
+
+    Class[] readClasses() throws IOException {
+        //  class_bands:
+        //        *class_this :DELTA5  (cp_Class)
+        //        *class_super :DELTA5  (cp_Class)
+        //        *class_interface_count :DELTA5
+        //        *class_interface :DELTA5  (cp_Class)
+        //        ...(member bands)...
+        //        class_attr_bands
+        //        code_bands
+        Class[] classes = new Class[numClasses];
+        if (verbose > 0)
+            Utils.log.info("  ...building "+classes.length+" classes...");
+
+        class_this.expectLength(numClasses);
+        class_super.expectLength(numClasses);
+        class_interface_count.expectLength(numClasses);
+
+        class_this.readFrom(in);
+        class_super.readFrom(in);
+        class_interface_count.readFrom(in);
+        class_interface.expectLength(class_interface_count.getIntTotal());
+        class_interface.readFrom(in);
+        for (int i = 0; i < classes.length; i++) {
+            ClassEntry   thisClass  = (ClassEntry) class_this.getRef();
+            ClassEntry   superClass = (ClassEntry) class_super.getRef();
+            ClassEntry[] interfaces = new ClassEntry[class_interface_count.getInt()];
+            for (int j = 0; j < interfaces.length; j++) {
+                interfaces[j] = (ClassEntry) class_interface.getRef();
+            }
+            // Packer encoded rare case of null superClass as thisClass:
+            if (superClass == thisClass)  superClass = null;
+            Class cls = pkg.new Class(NO_FLAGS_YET,
+                                      thisClass, superClass, interfaces);
+            classes[i] = cls;
+        }
+        class_this.doneDisbursing();
+        class_super.doneDisbursing();
+        class_interface_count.doneDisbursing();
+        class_interface.doneDisbursing();
+        readMembers(classes);
+        countAndReadAttrs(ATTR_CONTEXT_CLASS, Arrays.asList(classes));
+        pkg.trimToSize();
+        readCodeHeaders();
+        //code_bands.doneDisbursing(); // still need to read code attrs
+        //class_bands.doneDisbursing(); // still need to read code attrs
+        return classes;
+    }
+
+    private int getOutputIndex(Entry e) {
+        // Output CPs do not contain signatures.
+        assert(e.tag != CONSTANT_Signature);
+        int k = pkg.cp.untypedIndexOf(e);
+        // In the output ordering, input signatures can serve
+        // in place of Utf8s.
+        if (k >= 0)
+            return k;
+        if (e.tag == CONSTANT_Utf8) {
+            Entry se = utf8Signatures.get(e);
+            return pkg.cp.untypedIndexOf(se);
+        }
+        return -1;
+    }
+
+    Comparator<Entry> entryOutputOrder = new Comparator<>() {
+        public int compare(Entry e0, Entry e1) {
+            int k0 = getOutputIndex(e0);
+            int k1 = getOutputIndex(e1);
+            if (k0 >= 0 && k1 >= 0)
+                // If both have keys, use the keys.
+                return k0 - k1;
+            if (k0 == k1)
+                // If neither have keys, use their native tags & spellings.
+                return e0.compareTo(e1);
+            // Otherwise, the guy with the key comes first.
+            return (k0 >= 0)? 0-1: 1-0;
+        }
+    };
+
+    void reconstructClass(Class cls) {
+        if (verbose > 1)  Utils.log.fine("reconstruct "+cls);
+
+        // check for local .ClassFile.version
+        Attribute retroVersion = cls.getAttribute(attrClassFileVersion);
+        if (retroVersion != null) {
+            cls.removeAttribute(retroVersion);
+            cls.version = parseClassFileVersionAttr(retroVersion);
+        } else {
+            cls.version = pkg.defaultClassVersion;
+        }
+
+        // Replace null SourceFile by "obvious" string.
+        cls.expandSourceFile();
+
+        // record the local cp:
+        cls.setCPMap(reconstructLocalCPMap(cls));
+    }
+
+    Entry[] reconstructLocalCPMap(Class cls) {
+        Set<Entry> ldcRefs = ldcRefMap.get(cls);
+        Set<Entry> cpRefs = new HashSet<>();
+
+        // look for constant pool entries:
+        cls.visitRefs(VRM_CLASSIC, cpRefs);
+
+        ArrayList<BootstrapMethodEntry> bsms = new ArrayList<>();
+        // flesh out the local constant pool
+        ConstantPool.completeReferencesIn(cpRefs, true, bsms);
+
+        // add the bsm and references as required
+        if (!bsms.isEmpty()) {
+            cls.addAttribute(Package.attrBootstrapMethodsEmpty.canonicalInstance());
+            cpRefs.add(Package.getRefString("BootstrapMethods"));
+            Collections.sort(bsms);
+            cls.setBootstrapMethods(bsms);
+        }
+
+        // Now that we know all our local class references,
+        // compute the InnerClasses attribute.
+        // An InnerClasses attribute usually gets added here,
+        // although it might already have been present.
+        int changed = cls.expandLocalICs();
+
+        if (changed != 0) {
+            if (changed > 0) {
+                // Just visit the expanded InnerClasses attr.
+                cls.visitInnerClassRefs(VRM_CLASSIC, cpRefs);
+            } else {
+                // Have to recompute from scratch, because of deletions.
+                cpRefs.clear();
+                cls.visitRefs(VRM_CLASSIC, cpRefs);
+            }
+
+            // flesh out the local constant pool, again
+            ConstantPool.completeReferencesIn(cpRefs, true, bsms);
+        }
+
+        // construct a local constant pool
+        int numDoubles = 0;
+        for (Entry e : cpRefs) {
+            if (e.isDoubleWord())  numDoubles++;
+        }
+        Entry[] cpMap = new Entry[1+numDoubles+cpRefs.size()];
+        int fillp = 1;
+
+        // Add all ldc operands first.
+        if (ldcRefs != null) {
+            assert(cpRefs.containsAll(ldcRefs));
+            for (Entry e : ldcRefs) {
+                cpMap[fillp++] = e;
+            }
+            assert(fillp == 1+ldcRefs.size());
+            cpRefs.removeAll(ldcRefs);
+            ldcRefs = null;  // done with it
+        }
+
+        // Next add all the two-byte references.
+        Set<Entry> wideRefs = cpRefs;
+        cpRefs = null;  // do not use!
+        int narrowLimit = fillp;
+        for (Entry e : wideRefs) {
+            cpMap[fillp++] = e;
+        }
+        assert(fillp == narrowLimit+wideRefs.size());
+        Arrays.sort(cpMap, 1, narrowLimit, entryOutputOrder);
+        Arrays.sort(cpMap, narrowLimit, fillp, entryOutputOrder);
+
+        if (verbose > 3) {
+            Utils.log.fine("CP of "+this+" {");
+            for (int i = 0; i < fillp; i++) {
+                Entry e = cpMap[i];
+                Utils.log.fine("  "+((e==null)?-1:getOutputIndex(e))
+                                   +" : "+e);
+            }
+            Utils.log.fine("}");
+        }
+
+        // Now repack backwards, introducing null elements.
+        int revp = cpMap.length;
+        for (int i = fillp; --i >= 1; ) {
+            Entry e = cpMap[i];
+            if (e.isDoubleWord())
+                cpMap[--revp] = null;
+            cpMap[--revp] = e;
+        }
+        assert(revp == 1);  // do not process the initial null
+
+        return cpMap;
+    }
+
+    void readMembers(Class[] classes) throws IOException {
+        //  class_bands:
+        //        ...
+        //        *class_field_count :DELTA5
+        //        *class_method_count :DELTA5
+        //
+        //        *field_descr :DELTA5  (cp_Descr)
+        //        field_attr_bands
+        //
+        //        *method_descr :MDELTA5  (cp_Descr)
+        //        method_attr_bands
+        //        ...
+        assert(classes.length == numClasses);
+        class_field_count.expectLength(numClasses);
+        class_method_count.expectLength(numClasses);
+        class_field_count.readFrom(in);
+        class_method_count.readFrom(in);
+
+        // Make a pre-pass over field and method counts to size the descrs:
+        int totalNF = class_field_count.getIntTotal();
+        int totalNM = class_method_count.getIntTotal();
+        field_descr.expectLength(totalNF);
+        method_descr.expectLength(totalNM);
+        if (verbose > 1)  Utils.log.fine("expecting #fields="+totalNF+
+                " and #methods="+totalNM+" in #classes="+numClasses);
+
+        List<Class.Field> fields = new ArrayList<>(totalNF);
+        field_descr.readFrom(in);
+        for (int i = 0; i < classes.length; i++) {
+            Class c = classes[i];
+            int nf = class_field_count.getInt();
+            for (int j = 0; j < nf; j++) {
+                Class.Field f = c.new Field(NO_FLAGS_YET, (DescriptorEntry)
+                                            field_descr.getRef());
+                fields.add(f);
+            }
+        }
+        class_field_count.doneDisbursing();
+        field_descr.doneDisbursing();
+        countAndReadAttrs(ATTR_CONTEXT_FIELD, fields);
+        fields = null;  // release to GC
+
+        List<Class.Method> methods = new ArrayList<>(totalNM);
+        method_descr.readFrom(in);
+        for (int i = 0; i < classes.length; i++) {
+            Class c = classes[i];
+            int nm = class_method_count.getInt();
+            for (int j = 0; j < nm; j++) {
+                Class.Method m = c.new Method(NO_FLAGS_YET, (DescriptorEntry)
+                                              method_descr.getRef());
+                methods.add(m);
+            }
+        }
+        class_method_count.doneDisbursing();
+        method_descr.doneDisbursing();
+        countAndReadAttrs(ATTR_CONTEXT_METHOD, methods);
+
+        // Up to this point, Code attributes look like empty attributes.
+        // Now we start to special-case them.  The empty canonical Code
+        // attributes stay in the method attribute lists, however.
+        allCodes = buildCodeAttrs(methods);
+    }
+
+    Code[] allCodes;
+    List<Code> codesWithFlags;
+    Map<Class, Set<Entry>> ldcRefMap = new HashMap<>();
+
+    Code[] buildCodeAttrs(List<Class.Method> methods) {
+        List<Code> codes = new ArrayList<>(methods.size());
+        for (Class.Method m : methods) {
+            if (m.getAttribute(attrCodeEmpty) != null) {
+                m.code = new Code(m);
+                codes.add(m.code);
+            }
+        }
+        Code[] a = new Code[codes.size()];
+        codes.toArray(a);
+        return a;
+    }
+
+    void readCodeHeaders() throws IOException {
+        //  code_bands:
+        //        *code_headers :BYTE1
+        //
+        //        *code_max_stack :UNSIGNED5
+        //        *code_max_na_locals :UNSIGNED5
+        //        *code_handler_count :UNSIGNED5
+        //        ...
+        //        code_attr_bands
+        boolean attrsOK = testBit(archiveOptions, AO_HAVE_ALL_CODE_FLAGS);
+        code_headers.expectLength(allCodes.length);
+        code_headers.readFrom(in);
+        List<Code> longCodes = new ArrayList<>(allCodes.length / 10);
+        for (int i = 0; i < allCodes.length; i++) {
+            Code c = allCodes[i];
+            int sc = code_headers.getByte();
+            assert(sc == (sc & 0xFF));
+            if (verbose > 2)
+                Utils.log.fine("codeHeader "+c+" = "+sc);
+            if (sc == LONG_CODE_HEADER) {
+                // We will read ms/ml/nh/flags from bands shortly.
+                longCodes.add(c);
+                continue;
+            }
+            // Short code header is the usual case:
+            c.setMaxStack(     shortCodeHeader_max_stack(sc) );
+            c.setMaxNALocals(  shortCodeHeader_max_na_locals(sc) );
+            c.setHandlerCount( shortCodeHeader_handler_count(sc) );
+            assert(shortCodeHeader(c) == sc);
+        }
+        code_headers.doneDisbursing();
+        code_max_stack.expectLength(longCodes.size());
+        code_max_na_locals.expectLength(longCodes.size());
+        code_handler_count.expectLength(longCodes.size());
+
+        // Do the long headers now.
+        code_max_stack.readFrom(in);
+        code_max_na_locals.readFrom(in);
+        code_handler_count.readFrom(in);
+        for (Code c : longCodes) {
+            c.setMaxStack(     code_max_stack.getInt() );
+            c.setMaxNALocals(  code_max_na_locals.getInt() );
+            c.setHandlerCount( code_handler_count.getInt() );
+        }
+        code_max_stack.doneDisbursing();
+        code_max_na_locals.doneDisbursing();
+        code_handler_count.doneDisbursing();
+
+        readCodeHandlers();
+
+        if (attrsOK) {
+            // Code attributes are common (debug info not stripped).
+            codesWithFlags = Arrays.asList(allCodes);
+        } else {
+            // Code attributes are very sparse (debug info is stripped).
+            codesWithFlags = longCodes;
+        }
+        countAttrs(ATTR_CONTEXT_CODE, codesWithFlags);
+        // do readAttrs later, after BCs are scanned
+    }
+
+    void readCodeHandlers() throws IOException {
+        //  code_bands:
+        //        ...
+        //        *code_handler_start_P :BCI5
+        //        *code_handler_end_PO :BRANCH5
+        //        *code_handler_catch_PO :BRANCH5
+        //        *code_handler_class_RCN :UNSIGNED5  (null or cp_Class)
+        //        ...
+        int nh = 0;
+        for (int i = 0; i < allCodes.length; i++) {
+            Code c = allCodes[i];
+            nh += c.getHandlerCount();
+        }
+
+        ValueBand[] code_handler_bands = {
+            code_handler_start_P,
+            code_handler_end_PO,
+            code_handler_catch_PO,
+            code_handler_class_RCN
+        };
+
+        for (int i = 0; i < code_handler_bands.length; i++) {
+            code_handler_bands[i].expectLength(nh);
+            code_handler_bands[i].readFrom(in);
+        }
+
+        for (int i = 0; i < allCodes.length; i++) {
+            Code c = allCodes[i];
+            for (int j = 0, jmax = c.getHandlerCount(); j < jmax; j++) {
+                c.handler_class[j] = code_handler_class_RCN.getRef();
+                // For now, just record the raw BCI codes.
+                // We must wait until we have instruction boundaries.
+                c.handler_start[j] = code_handler_start_P.getInt();
+                c.handler_end[j]   = code_handler_end_PO.getInt();
+                c.handler_catch[j] = code_handler_catch_PO.getInt();
+            }
+        }
+        for (int i = 0; i < code_handler_bands.length; i++) {
+            code_handler_bands[i].doneDisbursing();
+        }
+    }
+
+    void fixupCodeHandlers() {
+        // Actually decode (renumber) the BCIs now.
+        for (int i = 0; i < allCodes.length; i++) {
+            Code c = allCodes[i];
+            for (int j = 0, jmax = c.getHandlerCount(); j < jmax; j++) {
+                int sum = c.handler_start[j];
+                c.handler_start[j] = c.decodeBCI(sum);
+                sum += c.handler_end[j];
+                c.handler_end[j]   = c.decodeBCI(sum);
+                sum += c.handler_catch[j];
+                c.handler_catch[j] = c.decodeBCI(sum);
+            }
+        }
+    }
+
+    // Generic routines for reading attributes of
+    // classes, fields, methods, and codes.
+    // The holders is a global list, already collected,
+    // of attribute "customers".
+    void countAndReadAttrs(int ctype, Collection<? extends Attribute.Holder> holders)
+            throws IOException {
+        //  class_attr_bands:
+        //        *class_flags :UNSIGNED5
+        //        *class_attr_count :UNSIGNED5
+        //        *class_attr_indexes :UNSIGNED5
+        //        *class_attr_calls :UNSIGNED5
+        //        *class_Signature_RS :UNSIGNED5 (cp_Signature)
+        //        class_metadata_bands
+        //        *class_SourceFile_RU :UNSIGNED5 (cp_Utf8)
+        //        *class_EnclosingMethod_RM :UNSIGNED5 (cp_Method)
+        //        ic_local_bands
+        //        *class_ClassFile_version_minor_H :UNSIGNED5
+        //        *class_ClassFile_version_major_H :UNSIGNED5
+        //        class_type_metadata_bands
+        //
+        //  field_attr_bands:
+        //        *field_flags :UNSIGNED5
+        //        *field_attr_count :UNSIGNED5
+        //        *field_attr_indexes :UNSIGNED5
+        //        *field_attr_calls :UNSIGNED5
+        //        *field_Signature_RS :UNSIGNED5 (cp_Signature)
+        //        field_metadata_bands
+        //        *field_ConstantValue_KQ :UNSIGNED5 (cp_Int, etc.; see note)
+        //        field_type_metadata_bands
+        //
+        //  method_attr_bands:
+        //        *method_flags :UNSIGNED5
+        //        *method_attr_count :UNSIGNED5
+        //        *method_attr_indexes :UNSIGNED5
+        //        *method_attr_calls :UNSIGNED5
+        //        *method_Signature_RS :UNSIGNED5 (cp_Signature)
+        //        method_metadata_bands
+        //        *method_Exceptions_N :UNSIGNED5
+        //        *method_Exceptions_RC :UNSIGNED5  (cp_Class)
+        //        *method_MethodParameters_NB: BYTE1
+        //        *method_MethodParameters_RUN: UNSIGNED5 (cp_Utf8)
+        //        *method_MethodParameters_FH:  UNSIGNED5 (flag)
+        //        method_type_metadata_bands
+        //
+        //  code_attr_bands:
+        //        *code_flags :UNSIGNED5
+        //        *code_attr_count :UNSIGNED5
+        //        *code_attr_indexes :UNSIGNED5
+        //        *code_attr_calls :UNSIGNED5
+        //        *code_LineNumberTable_N :UNSIGNED5
+        //        *code_LineNumberTable_bci_P :BCI5
+        //        *code_LineNumberTable_line :UNSIGNED5
+        //        *code_LocalVariableTable_N :UNSIGNED5
+        //        *code_LocalVariableTable_bci_P :BCI5
+        //        *code_LocalVariableTable_span_O :BRANCH5
+        //        *code_LocalVariableTable_name_RU :UNSIGNED5 (cp_Utf8)
+        //        *code_LocalVariableTable_type_RS :UNSIGNED5 (cp_Signature)
+        //        *code_LocalVariableTable_slot :UNSIGNED5
+        //        code_type_metadata_bands
+
+        countAttrs(ctype, holders);
+        readAttrs(ctype, holders);
+    }
+
+    // Read flags and count the attributes that are to be placed
+    // on the given holders.
+    void countAttrs(int ctype, Collection<? extends Attribute.Holder> holders)
+            throws IOException {
+        // Here, xxx stands for one of class, field, method, code.
+        MultiBand xxx_attr_bands = attrBands[ctype];
+        long flagMask = attrFlagMask[ctype];
+        if (verbose > 1) {
+            Utils.log.fine("scanning flags and attrs for "+
+                    Attribute.contextName(ctype)+"["+holders.size()+"]");
+        }
+
+        // Fetch the attribute layout definitions which govern the bands
+        // we are about to read.
+        List<Attribute.Layout> defList = attrDefs.get(ctype);
+        Attribute.Layout[] defs = new Attribute.Layout[defList.size()];
+        defList.toArray(defs);
+        IntBand xxx_flags_hi = getAttrBand(xxx_attr_bands, AB_FLAGS_HI);
+        IntBand xxx_flags_lo = getAttrBand(xxx_attr_bands, AB_FLAGS_LO);
+        IntBand xxx_attr_count = getAttrBand(xxx_attr_bands, AB_ATTR_COUNT);
+        IntBand xxx_attr_indexes = getAttrBand(xxx_attr_bands, AB_ATTR_INDEXES);
+        IntBand xxx_attr_calls = getAttrBand(xxx_attr_bands, AB_ATTR_CALLS);
+
+        // Count up the number of holders which have overflow attrs.
+        int overflowMask = attrOverflowMask[ctype];
+        int overflowHolderCount = 0;
+        boolean haveLongFlags = haveFlagsHi(ctype);
+        xxx_flags_hi.expectLength(haveLongFlags? holders.size(): 0);
+        xxx_flags_hi.readFrom(in);
+        xxx_flags_lo.expectLength(holders.size());
+        xxx_flags_lo.readFrom(in);
+        assert((flagMask & overflowMask) == overflowMask);
+        for (Attribute.Holder h : holders) {
+            int flags = xxx_flags_lo.getInt();
+            h.flags = flags;
+            if ((flags & overflowMask) != 0)
+                overflowHolderCount += 1;
+        }
+
+        // For each holder with overflow attrs, read a count.
+        xxx_attr_count.expectLength(overflowHolderCount);
+        xxx_attr_count.readFrom(in);
+        xxx_attr_indexes.expectLength(xxx_attr_count.getIntTotal());
+        xxx_attr_indexes.readFrom(in);
+
+        // Now it's time to check flag bits that indicate attributes.
+        // We accumulate (a) a list of attribute types for each holder
+        // (class/field/method/code), and also we accumulate (b) a total
+        // count for each attribute type.
+        int[] totalCounts = new int[defs.length];
+        for (Attribute.Holder h : holders) {
+            assert(h.attributes == null);
+            // System.out.println("flags="+h.flags+" using fm="+flagMask);
+            long attrBits = ((h.flags & flagMask) << 32) >>> 32;
+            // Clean up the flags now.
+            h.flags -= (int)attrBits;   // strip attr bits
+            assert(h.flags == (char)h.flags);  // 16 bits only now
+            assert((ctype != ATTR_CONTEXT_CODE) || h.flags == 0);
+            if (haveLongFlags)
+                attrBits += (long)xxx_flags_hi.getInt() << 32;
+            if (attrBits == 0)  continue;  // no attrs on this guy
+
+            int noa = 0;  // number of overflow attrs
+            long overflowBit = (attrBits & overflowMask);
+            assert(overflowBit >= 0);
+            attrBits -= overflowBit;
+            if (overflowBit != 0) {
+                noa = xxx_attr_count.getInt();
+            }
+
+            int nfa = 0;  // number of flag attrs
+            long bits = attrBits;
+            for (int ai = 0; bits != 0; ai++) {
+                if ((bits & (1L<<ai)) == 0)  continue;
+                bits -= (1L<<ai);
+                nfa += 1;
+            }
+            List<Attribute> ha = new ArrayList<>(nfa + noa);
+            h.attributes = ha;
+            bits = attrBits;  // iterate again
+            for (int ai = 0; bits != 0; ai++) {
+                if ((bits & (1L<<ai)) == 0)  continue;
+                bits -= (1L<<ai);
+                totalCounts[ai] += 1;
+                // This definition index is live in this holder.
+                if (defs[ai] == null)  badAttrIndex(ai, ctype);
+                Attribute canonical = defs[ai].canonicalInstance();
+                ha.add(canonical);
+                nfa -= 1;
+            }
+            assert(nfa == 0);
+            for (; noa > 0; noa--) {
+                int ai = xxx_attr_indexes.getInt();
+                totalCounts[ai] += 1;
+                // This definition index is live in this holder.
+                if (defs[ai] == null)  badAttrIndex(ai, ctype);
+                Attribute canonical = defs[ai].canonicalInstance();
+                ha.add(canonical);
+            }
+        }
+
+        xxx_flags_hi.doneDisbursing();
+        xxx_flags_lo.doneDisbursing();
+        xxx_attr_count.doneDisbursing();
+        xxx_attr_indexes.doneDisbursing();
+
+        // Now each holder has a list of canonical attribute instances.
+        // For layouts with no elements, we are done.  However, for
+        // layouts with bands, we must replace each canonical (empty)
+        // instance with a value-bearing one, initialized from the
+        // appropriate bands.
+
+        // Make a small pass to detect and read backward call counts.
+        int callCounts = 0;
+        for (boolean predef = true; ; predef = false) {
+            for (int ai = 0; ai < defs.length; ai++) {
+                Attribute.Layout def = defs[ai];
+                if (def == null)  continue;  // unused index
+                if (predef != isPredefinedAttr(ctype, ai))
+                    continue;  // wrong pass
+                int totalCount = totalCounts[ai];
+                if (totalCount == 0)
+                    continue;  // irrelevant
+                Attribute.Layout.Element[] cbles = def.getCallables();
+                for (int j = 0; j < cbles.length; j++) {
+                    assert(cbles[j].kind == Attribute.EK_CBLE);
+                    if (cbles[j].flagTest(Attribute.EF_BACK))
+                        callCounts += 1;
+                }
+            }
+            if (!predef)  break;
+        }
+        xxx_attr_calls.expectLength(callCounts);
+        xxx_attr_calls.readFrom(in);
+
+        // Finally, size all the attribute bands.
+        for (boolean predef = true; ; predef = false) {
+            for (int ai = 0; ai < defs.length; ai++) {
+                Attribute.Layout def = defs[ai];
+                if (def == null)  continue;  // unused index
+                if (predef != isPredefinedAttr(ctype, ai))
+                    continue;  // wrong pass
+                int totalCount = totalCounts[ai];
+                Band[] ab = attrBandTable.get(def);
+                if (def == attrInnerClassesEmpty) {
+                    // Special case.
+                    // Size the bands as if using the following layout:
+                    //    [RCH TI[ (0)[] ()[RCNH RUNH] ]].
+                    class_InnerClasses_N.expectLength(totalCount);
+                    class_InnerClasses_N.readFrom(in);
+                    int tupleCount = class_InnerClasses_N.getIntTotal();
+                    class_InnerClasses_RC.expectLength(tupleCount);
+                    class_InnerClasses_RC.readFrom(in);
+                    class_InnerClasses_F.expectLength(tupleCount);
+                    class_InnerClasses_F.readFrom(in);
+                    // Drop remaining columns wherever flags are zero:
+                    tupleCount -= class_InnerClasses_F.getIntCount(0);
+                    class_InnerClasses_outer_RCN.expectLength(tupleCount);
+                    class_InnerClasses_outer_RCN.readFrom(in);
+                    class_InnerClasses_name_RUN.expectLength(tupleCount);
+                    class_InnerClasses_name_RUN.readFrom(in);
+                } else if (!optDebugBands && totalCount == 0) {
+                    // Expect no elements at all.  Skip quickly. however if we
+                    // are debugging bands, read all bands regardless
+                    for (int j = 0; j < ab.length; j++) {
+                        ab[j].doneWithUnusedBand();
+                    }
+                } else {
+                    // Read these bands in sequence.
+                    boolean hasCallables = def.hasCallables();
+                    if (!hasCallables) {
+                        readAttrBands(def.elems, totalCount, new int[0], ab);
+                    } else {
+                        Attribute.Layout.Element[] cbles = def.getCallables();
+                        // At first, record initial calls.
+                        // Later, forward calls may also accumulate here:
+                        int[] forwardCounts = new int[cbles.length];
+                        forwardCounts[0] = totalCount;
+                        for (int j = 0; j < cbles.length; j++) {
+                            assert(cbles[j].kind == Attribute.EK_CBLE);
+                            int entryCount = forwardCounts[j];
+                            forwardCounts[j] = -1;  // No more, please!
+                            if (totalCount > 0 && cbles[j].flagTest(Attribute.EF_BACK))
+                                entryCount += xxx_attr_calls.getInt();
+                            readAttrBands(cbles[j].body, entryCount, forwardCounts, ab);
+                        }
+                    }
+                    // mark them read,  to satisfy asserts
+                    if (optDebugBands && totalCount == 0) {
+                        for (int j = 0; j < ab.length; j++) {
+                            ab[j].doneDisbursing();
+                        }
+                    }
+                }
+            }
+            if (!predef)  break;
+        }
+        xxx_attr_calls.doneDisbursing();
+    }
+
+    void badAttrIndex(int ai, int ctype) throws IOException {
+        throw new IOException("Unknown attribute index "+ai+" for "+
+                                   ATTR_CONTEXT_NAME[ctype]+" attribute");
+    }
+
+    void readAttrs(int ctype, Collection<? extends Attribute.Holder> holders)
+            throws IOException {
+        // Decode band values into attributes.
+        Set<Attribute.Layout> sawDefs = new HashSet<>();
+        ByteArrayOutputStream buf = new ByteArrayOutputStream();
+        for (final Attribute.Holder h : holders) {
+            if (h.attributes == null)  continue;
+            for (ListIterator<Attribute> j = h.attributes.listIterator(); j.hasNext(); ) {
+                Attribute a = j.next();
+                Attribute.Layout def = a.layout();
+                if (def.bandCount == 0) {
+                    if (def == attrInnerClassesEmpty) {
+                        // Special logic to read this attr.
+                        readLocalInnerClasses((Class) h);
+                        continue;
+                    }
+                    // Canonical empty attr works fine (e.g., Synthetic).
+                    continue;
+                }
+                sawDefs.add(def);
+                boolean isCV = (ctype == ATTR_CONTEXT_FIELD && def == attrConstantValue);
+                if (isCV)  setConstantValueIndex((Class.Field)h);
+                if (verbose > 2)
+                    Utils.log.fine("read "+a+" in "+h);
+                final Band[] ab = attrBandTable.get(def);
+                // Read one attribute of type def from ab into a byte array.
+                buf.reset();
+                Object fixups = a.unparse(new Attribute.ValueStream() {
+                    public int getInt(int bandIndex) {
+                        return ((IntBand) ab[bandIndex]).getInt();
+                    }
+                    public Entry getRef(int bandIndex) {
+                        return ((CPRefBand) ab[bandIndex]).getRef();
+                    }
+                    public int decodeBCI(int bciCode) {
+                        Code code = (Code) h;
+                        return code.decodeBCI(bciCode);
+                    }
+                }, buf);
+                // Replace the canonical attr with the one just read.
+                j.set(a.addContent(buf.toByteArray(), fixups));
+                if (isCV)  setConstantValueIndex(null);  // clean up
+            }
+        }
+
+        // Mark the bands we just used as done disbursing.
+        for (Attribute.Layout def : sawDefs) {
+            if (def == null)  continue;  // unused index
+            Band[] ab = attrBandTable.get(def);
+            for (int j = 0; j < ab.length; j++) {
+                ab[j].doneDisbursing();
+            }
+        }
+
+        if (ctype == ATTR_CONTEXT_CLASS) {
+            class_InnerClasses_N.doneDisbursing();
+            class_InnerClasses_RC.doneDisbursing();
+            class_InnerClasses_F.doneDisbursing();
+            class_InnerClasses_outer_RCN.doneDisbursing();
+            class_InnerClasses_name_RUN.doneDisbursing();
+        }
+
+        MultiBand xxx_attr_bands = attrBands[ctype];
+        for (int i = 0; i < xxx_attr_bands.size(); i++) {
+            Band b = xxx_attr_bands.get(i);
+            if (b instanceof MultiBand)
+                b.doneDisbursing();
+        }
+        xxx_attr_bands.doneDisbursing();
+    }
+
+    private
+    void readAttrBands(Attribute.Layout.Element[] elems,
+                       int count, int[] forwardCounts,
+                       Band[] ab)
+            throws IOException {
+        for (int i = 0; i < elems.length; i++) {
+            Attribute.Layout.Element e = elems[i];
+            Band eBand = null;
+            if (e.hasBand()) {
+                eBand = ab[e.bandIndex];
+                eBand.expectLength(count);
+                eBand.readFrom(in);
+            }
+            switch (e.kind) {
+            case Attribute.EK_REPL:
+                // Recursive call.
+                int repCount = ((IntBand)eBand).getIntTotal();
+                // Note:  getIntTotal makes an extra pass over this band.
+                readAttrBands(e.body, repCount, forwardCounts, ab);
+                break;
+            case Attribute.EK_UN:
+                int remainingCount = count;
+                for (int j = 0; j < e.body.length; j++) {
+                    int caseCount;
+                    if (j == e.body.length-1) {
+                        caseCount = remainingCount;
+                    } else {
+                        caseCount = 0;
+                        for (int j0 = j;
+                             (j == j0)
+                             || (j < e.body.length
+                                 && e.body[j].flagTest(Attribute.EF_BACK));
+                             j++) {
+                            caseCount += ((IntBand)eBand).getIntCount(e.body[j].value);
+                        }
+                        --j;  // back up to last occurrence of this body
+                    }
+                    remainingCount -= caseCount;
+                    readAttrBands(e.body[j].body, caseCount, forwardCounts, ab);
+                }
+                assert(remainingCount == 0);
+                break;
+            case Attribute.EK_CALL:
+                assert(e.body.length == 1);
+                assert(e.body[0].kind == Attribute.EK_CBLE);
+                if (!e.flagTest(Attribute.EF_BACK)) {
+                    // Backward calls are pre-counted, but forwards are not.
+                    // Push the present count forward.
+                    assert(forwardCounts[e.value] >= 0);
+                    forwardCounts[e.value] += count;
+                }
+                break;
+            case Attribute.EK_CBLE:
+                assert(false);
+                break;
+            }
+        }
+    }
+
+    void readByteCodes() throws IOException {
+        //  bc_bands:
+        //        *bc_codes :BYTE1
+        //        *bc_case_count :UNSIGNED5
+        //        *bc_case_value :DELTA5
+        //        *bc_byte :BYTE1
+        //        *bc_short :DELTA5
+        //        *bc_local :UNSIGNED5
+        //        *bc_label :BRANCH5
+        //        *bc_intref :DELTA5  (cp_Int)
+        //        *bc_floatref :DELTA5  (cp_Float)
+        //        *bc_longref :DELTA5  (cp_Long)
+        //        *bc_doubleref :DELTA5  (cp_Double)
+        //        *bc_stringref :DELTA5  (cp_String)
+        //        *bc_classref :UNSIGNED5  (current class or cp_Class)
+        //        *bc_fieldref :DELTA5  (cp_Field)
+        //        *bc_methodref :UNSIGNED5  (cp_Method)
+        //        *bc_imethodref :DELTA5  (cp_Imethod)
+        //        *bc_thisfield :UNSIGNED5 (cp_Field, only for current class)
+        //        *bc_superfield :UNSIGNED5 (cp_Field, only for current super)
+        //        *bc_thismethod :UNSIGNED5 (cp_Method, only for current class)
+        //        *bc_supermethod :UNSIGNED5 (cp_Method, only for current super)
+        //        *bc_initref :UNSIGNED5 (cp_Field, only for most recent new)
+        //        *bc_escref :UNSIGNED5 (cp_All)
+        //        *bc_escrefsize :UNSIGNED5
+        //        *bc_escsize :UNSIGNED5
+        //        *bc_escbyte :BYTE1
+        bc_codes.elementCountForDebug = allCodes.length;
+        bc_codes.setInputStreamFrom(in);
+        readByteCodeOps();  // reads from bc_codes and bc_case_count
+        bc_codes.doneDisbursing();
+
+        // All the operand bands have now been sized.  Read them all in turn.
+        Band[] operand_bands = {
+            bc_case_value,
+            bc_byte, bc_short,
+            bc_local, bc_label,
+            bc_intref, bc_floatref,
+            bc_longref, bc_doubleref, bc_stringref,
+            bc_loadablevalueref,
+            bc_classref, bc_fieldref,
+            bc_methodref, bc_imethodref,
+            bc_indyref,
+            bc_thisfield, bc_superfield,
+            bc_thismethod, bc_supermethod,
+            bc_initref,
+            bc_escref, bc_escrefsize, bc_escsize
+        };
+        for (int i = 0; i < operand_bands.length; i++) {
+            operand_bands[i].readFrom(in);
+        }
+        bc_escbyte.expectLength(bc_escsize.getIntTotal());
+        bc_escbyte.readFrom(in);
+
+        expandByteCodeOps();
+
+        // Done fetching values from operand bands:
+        bc_case_count.doneDisbursing();
+        for (int i = 0; i < operand_bands.length; i++) {
+            operand_bands[i].doneDisbursing();
+        }
+        bc_escbyte.doneDisbursing();
+        bc_bands.doneDisbursing();
+
+        // We must delay the parsing of Code attributes until we
+        // have a complete model of bytecodes, for BCI encodings.
+        readAttrs(ATTR_CONTEXT_CODE, codesWithFlags);
+        // Ditto for exception handlers in codes.
+        fixupCodeHandlers();
+        // Now we can finish with class_bands; cf. readClasses().
+        code_bands.doneDisbursing();
+        class_bands.doneDisbursing();
+    }
+
+    private void readByteCodeOps() throws IOException {
+        // scratch buffer for collecting code::
+        byte[] buf = new byte[1<<12];
+        // record of all switch opcodes (these are variable-length)
+        List<Integer> allSwitchOps = new ArrayList<>();
+        for (int k = 0; k < allCodes.length; k++) {
+            Code c = allCodes[k];
+        scanOneMethod:
+            for (int i = 0; ; i++) {
+                int bc = bc_codes.getByte();
+                if (i + 10 > buf.length)  buf = realloc(buf);
+                buf[i] = (byte)bc;
+                boolean isWide = false;
+                if (bc == _wide) {
+                    bc = bc_codes.getByte();
+                    buf[++i] = (byte)bc;
+                    isWide = true;
+                }
+                assert(bc == (0xFF & bc));
+                // Adjust expectations of various band sizes.
+                switch (bc) {
+                case _tableswitch:
+                case _lookupswitch:
+                    bc_case_count.expectMoreLength(1);
+                    allSwitchOps.add(bc);
+                    break;
+                case _iinc:
+                    bc_local.expectMoreLength(1);
+                    if (isWide)
+                        bc_short.expectMoreLength(1);
+                    else
+                        bc_byte.expectMoreLength(1);
+                    break;
+                case _sipush:
+                    bc_short.expectMoreLength(1);
+                    break;
+                case _bipush:
+                    bc_byte.expectMoreLength(1);
+                    break;
+                case _newarray:
+                    bc_byte.expectMoreLength(1);
+                    break;
+                case _multianewarray:
+                    assert(getCPRefOpBand(bc) == bc_classref);
+                    bc_classref.expectMoreLength(1);
+                    bc_byte.expectMoreLength(1);
+                    break;
+                case _ref_escape:
+                    bc_escrefsize.expectMoreLength(1);
+                    bc_escref.expectMoreLength(1);
+                    break;
+                case _byte_escape:
+                    bc_escsize.expectMoreLength(1);
+                    // bc_escbyte will have to be counted too
+                    break;
+                default:
+                    if (Instruction.isInvokeInitOp(bc)) {
+                        bc_initref.expectMoreLength(1);
+                        break;
+                    }
+                    if (Instruction.isSelfLinkerOp(bc)) {
+                        CPRefBand bc_which = selfOpRefBand(bc);
+                        bc_which.expectMoreLength(1);
+                        break;
+                    }
+                    if (Instruction.isBranchOp(bc)) {
+                        bc_label.expectMoreLength(1);
+                        break;
+                    }
+                    if (Instruction.isCPRefOp(bc)) {
+                        CPRefBand bc_which = getCPRefOpBand(bc);
+                        bc_which.expectMoreLength(1);
+                        assert(bc != _multianewarray);  // handled elsewhere
+                        break;
+                    }
+                    if (Instruction.isLocalSlotOp(bc)) {
+                        bc_local.expectMoreLength(1);
+                        break;
+                    }
+                    break;
+                case _end_marker:
+                    {
+                        // Transfer from buf to a more permanent place:
+                        c.bytes = realloc(buf, i);
+                        break scanOneMethod;
+                    }
+                }
+            }
+        }
+
+        // To size instruction bands correctly, we need info on switches:
+        bc_case_count.readFrom(in);
+        for (Integer i : allSwitchOps) {
+            int bc = i.intValue();
+            int caseCount = bc_case_count.getInt();
+            bc_label.expectMoreLength(1+caseCount); // default label + cases
+            bc_case_value.expectMoreLength(bc == _tableswitch ? 1 : caseCount);
+        }
+        bc_case_count.resetForSecondPass();
+    }
+
+    private void expandByteCodeOps() throws IOException {
+        // scratch buffer for collecting code:
+        byte[] buf = new byte[1<<12];
+        // scratch buffer for collecting instruction boundaries:
+        int[] insnMap = new int[1<<12];
+        // list of label carriers, for label decoding post-pass:
+        int[] labels = new int[1<<10];
+        // scratch buffer for registering CP refs:
+        Fixups fixupBuf = new Fixups();
+
+        for (int k = 0; k < allCodes.length; k++) {
+            Code code = allCodes[k];
+            byte[] codeOps = code.bytes;
+            code.bytes = null;  // just for now, while we accumulate bits
+
+            Class curClass = code.thisClass();
+
+            Set<Entry> ldcRefSet = ldcRefMap.get(curClass);
+            if (ldcRefSet == null)
+                ldcRefMap.put(curClass, ldcRefSet = new HashSet<>());
+
+            ClassEntry thisClass  = curClass.thisClass;
+            ClassEntry superClass = curClass.superClass;
+            ClassEntry newClass   = null;  // class of last _new opcode
+
+            int pc = 0;  // fill pointer in buf; actual bytecode PC
+            int numInsns = 0;
+            int numLabels = 0;
+            boolean hasEscs = false;
+            fixupBuf.clear();
+            for (int i = 0; i < codeOps.length; i++) {
+                int bc = Instruction.getByte(codeOps, i);
+                int curPC = pc;
+                insnMap[numInsns++] = curPC;
+                if (pc + 10 > buf.length)  buf = realloc(buf);
+                if (numInsns+10 > insnMap.length)  insnMap = realloc(insnMap);
+                if (numLabels+10 > labels.length)  labels = realloc(labels);
+                boolean isWide = false;
+                if (bc == _wide) {
+                    buf[pc++] = (byte) bc;
+                    bc = Instruction.getByte(codeOps, ++i);
+                    isWide = true;
+                }
+                switch (bc) {
+                case _tableswitch: // apc:  (df, lo, hi, (hi-lo+1)*(label))
+                case _lookupswitch: // apc:  (df, nc, nc*(case, label))
+                    {
+                        int caseCount = bc_case_count.getInt();
+                        while ((pc + 30 + caseCount*8) > buf.length)
+                            buf = realloc(buf);
+                        buf[pc++] = (byte) bc;
+                        //initialize apc, df, lo, hi bytes to reasonable bits:
+                        Arrays.fill(buf, pc, pc+30, (byte)0);
+                        Instruction.Switch isw = (Instruction.Switch)
+                            Instruction.at(buf, curPC);
+                        //isw.setDefaultLabel(getLabel(bc_label, code, curPC));
+                        isw.setCaseCount(caseCount);
+                        if (bc == _tableswitch) {
+                            isw.setCaseValue(0, bc_case_value.getInt());
+                        } else {
+                            for (int j = 0; j < caseCount; j++) {
+                                isw.setCaseValue(j, bc_case_value.getInt());
+                            }
+                        }
+                        // Make our getLabel calls later.
+                        labels[numLabels++] = curPC;
+                        pc = isw.getNextPC();
+                        continue;
+                    }
+                case _iinc:
+                    {
+                        buf[pc++] = (byte) bc;
+                        int local = bc_local.getInt();
+                        int delta;
+                        if (isWide) {
+                            delta = bc_short.getInt();
+                            Instruction.setShort(buf, pc, local); pc += 2;
+                            Instruction.setShort(buf, pc, delta); pc += 2;
+                        } else {
+                            delta = (byte) bc_byte.getByte();
+                            buf[pc++] = (byte)local;
+                            buf[pc++] = (byte)delta;
+                        }
+                        continue;
+                    }
+                case _sipush:
+                    {
+                        int val = bc_short.getInt();
+                        buf[pc++] = (byte) bc;
+                        Instruction.setShort(buf, pc, val); pc += 2;
+                        continue;
+                    }
+                case _bipush:
+                case _newarray:
+                    {
+                        int val = bc_byte.getByte();
+                        buf[pc++] = (byte) bc;
+                        buf[pc++] = (byte) val;
+                        continue;
+                    }
+                case _ref_escape:
+                    {
+                        // Note that insnMap has one entry for this.
+                        hasEscs = true;
+                        int size = bc_escrefsize.getInt();
+                        Entry ref = bc_escref.getRef();
+                        if (size == 1)  ldcRefSet.add(ref);
+                        int fmt;
+                        switch (size) {
+                        case 1: fixupBuf.addU1(pc, ref); break;
+                        case 2: fixupBuf.addU2(pc, ref); break;
+                        default: assert(false); fmt = 0;
+                        }
+                        buf[pc+0] = buf[pc+1] = 0;
+                        pc += size;
+                    }
+                    continue;
+                case _byte_escape:
+                    {
+                        // Note that insnMap has one entry for all these bytes.
+                        hasEscs = true;
+                        int size = bc_escsize.getInt();
+                        while ((pc + size) > buf.length)
+                            buf = realloc(buf);
+                        while (size-- > 0) {
+                            buf[pc++] = (byte) bc_escbyte.getByte();
+                        }
+                    }
+                    continue;
+                default:
+                    if (Instruction.isInvokeInitOp(bc)) {
+                        int idx = (bc - _invokeinit_op);
+                        int origBC = _invokespecial;
+                        ClassEntry classRef;
+                        switch (idx) {
+                        case _invokeinit_self_option:
+                            classRef = thisClass; break;
+                        case _invokeinit_super_option:
+                            classRef = superClass; break;
+                        default:
+                            assert(idx == _invokeinit_new_option);
+                            classRef = newClass; break;
+                        }
+                        buf[pc++] = (byte) origBC;
+                        int coding = bc_initref.getInt();
+                        // Find the nth overloading of <init> in classRef.
+                        MemberEntry ref = pkg.cp.getOverloadingForIndex(CONSTANT_Methodref, classRef, "<init>", coding);
+                        fixupBuf.addU2(pc, ref);
+                        buf[pc+0] = buf[pc+1] = 0;
+                        pc += 2;
+                        assert(Instruction.opLength(origBC) == (pc - curPC));
+                        continue;
+                    }
+                    if (Instruction.isSelfLinkerOp(bc)) {
+                        int idx = (bc - _self_linker_op);
+                        boolean isSuper = (idx >= _self_linker_super_flag);
+                        if (isSuper)  idx -= _self_linker_super_flag;
+                        boolean isAload = (idx >= _self_linker_aload_flag);
+                        if (isAload)  idx -= _self_linker_aload_flag;
+                        int origBC = _first_linker_op + idx;
+                        boolean isField = Instruction.isFieldOp(origBC);
+                        CPRefBand bc_which;
+                        ClassEntry which_cls  = isSuper ? superClass : thisClass;
+                        Index which_ix;
+                        if (isField) {
+                            bc_which = isSuper ? bc_superfield  : bc_thisfield;
+                            which_ix = pkg.cp.getMemberIndex(CONSTANT_Fieldref, which_cls);
+                        } else {
+                            bc_which = isSuper ? bc_supermethod : bc_thismethod;
+                            which_ix = pkg.cp.getMemberIndex(CONSTANT_Methodref, which_cls);
+                        }
+                        assert(bc_which == selfOpRefBand(bc));
+                        MemberEntry ref = (MemberEntry) bc_which.getRef(which_ix);
+                        if (isAload) {
+                            buf[pc++] = (byte) _aload_0;
+                            curPC = pc;
+                            // Note: insnMap keeps the _aload_0 separate.
+                            insnMap[numInsns++] = curPC;
+                        }
+                        buf[pc++] = (byte) origBC;
+                        fixupBuf.addU2(pc, ref);
+                        buf[pc+0] = buf[pc+1] = 0;
+                        pc += 2;
+                        assert(Instruction.opLength(origBC) == (pc - curPC));
+                        continue;
+                    }
+                    if (Instruction.isBranchOp(bc)) {
+                        buf[pc++] = (byte) bc;
+                        assert(!isWide);  // no wide prefix for branches
+                        int nextPC = curPC + Instruction.opLength(bc);
+                        // Make our getLabel calls later.
+                        labels[numLabels++] = curPC;
+                        //Instruction.at(buf, curPC).setBranchLabel(getLabel(bc_label, code, curPC));
+                        while (pc < nextPC)  buf[pc++] = 0;
+                        continue;
+                    }
+                    if (Instruction.isCPRefOp(bc)) {
+                        CPRefBand bc_which = getCPRefOpBand(bc);
+                        Entry ref = bc_which.getRef();
+                        if (ref == null) {
+                            if (bc_which == bc_classref) {
+                                // Shorthand for class self-references.
+                                ref = thisClass;
+                            } else {
+                                assert(false);
+                            }
+                        }
+                        int origBC = bc;
+                        int size = 2;
+                        switch (bc) {
+                        case _invokestatic_int:
+                            origBC = _invokestatic;
+                            break;
+                        case _invokespecial_int:
+                            origBC = _invokespecial;
+                            break;
+                        case _ildc:
+                        case _cldc:
+                        case _fldc:
+                        case _sldc:
+                        case _qldc:
+                            origBC = _ldc;
+                            size = 1;
+                            ldcRefSet.add(ref);
+                            break;
+                        case _ildc_w:
+                        case _cldc_w:
+                        case _fldc_w:
+                        case _sldc_w:
+                        case _qldc_w:
+                            origBC = _ldc_w;
+                            break;
+                        case _lldc2_w:
+                        case _dldc2_w:
+                            origBC = _ldc2_w;
+                            break;
+                        case _new:
+                            newClass = (ClassEntry) ref;
+                            break;
+                        }
+                        buf[pc++] = (byte) origBC;
+                        int fmt;
+                        switch (size) {
+                        case 1: fixupBuf.addU1(pc, ref); break;
+                        case 2: fixupBuf.addU2(pc, ref); break;
+                        default: assert(false); fmt = 0;
+                        }
+                        buf[pc+0] = buf[pc+1] = 0;
+                        pc += size;
+                        if (origBC == _multianewarray) {
+                            // Copy the trailing byte also.
+                            int val = bc_byte.getByte();
+                            buf[pc++] = (byte) val;
+                        } else if (origBC == _invokeinterface) {
+                            int argSize = ((MemberEntry)ref).descRef.typeRef.computeSize(true);
+                            buf[pc++] = (byte)( 1 + argSize );
+                            buf[pc++] = 0;
+                        } else if (origBC == _invokedynamic) {
+                            buf[pc++] = 0;
+                            buf[pc++] = 0;
+                        }
+                        assert(Instruction.opLength(origBC) == (pc - curPC));
+                        continue;
+                    }
+                    if (Instruction.isLocalSlotOp(bc)) {
+                        buf[pc++] = (byte) bc;
+                        int local = bc_local.getInt();
+                        if (isWide) {
+                            Instruction.setShort(buf, pc, local);
+                            pc += 2;
+                            if (bc == _iinc) {
+                                int iVal = bc_short.getInt();
+                                Instruction.setShort(buf, pc, iVal);
+                                pc += 2;
+                            }
+                        } else {
+                            Instruction.setByte(buf, pc, local);
+                            pc += 1;
+                            if (bc == _iinc) {
+                                int iVal = bc_byte.getByte();
+                                Instruction.setByte(buf, pc, iVal);
+                                pc += 1;
+                            }
+                        }
+                        assert(Instruction.opLength(bc) == (pc - curPC));
+                        continue;
+                    }
+                    // Random bytecode.  Just copy it.
+                    if (bc >= _bytecode_limit)
+                        Utils.log.warning("unrecognized bytescode "+bc
+                                            +" "+Instruction.byteName(bc));
+                    assert(bc < _bytecode_limit);
+                    buf[pc++] = (byte) bc;
+                    assert(Instruction.opLength(bc) == (pc - curPC));
+                    continue;
+                }
+            }
+            // now make a permanent copy of the bytecodes
+            code.setBytes(realloc(buf, pc));
+            code.setInstructionMap(insnMap, numInsns);
+            // fix up labels, now that code has its insnMap
+            Instruction ibr = null;  // temporary branch instruction
+            for (int i = 0; i < numLabels; i++) {
+                int curPC = labels[i];
+                // (Note:  Passing ibr in allows reuse, a speed hack.)
+                ibr = Instruction.at(code.bytes, curPC, ibr);
+                if (ibr instanceof Instruction.Switch) {
+                    Instruction.Switch isw = (Instruction.Switch) ibr;
+                    isw.setDefaultLabel(getLabel(bc_label, code, curPC));
+                    int caseCount = isw.getCaseCount();
+                    for (int j = 0; j < caseCount; j++) {
+                        isw.setCaseLabel(j, getLabel(bc_label, code, curPC));
+                    }
+                } else {
+                    ibr.setBranchLabel(getLabel(bc_label, code, curPC));
+                }
+            }
+            if (fixupBuf.size() > 0) {
+                if (verbose > 2)
+                    Utils.log.fine("Fixups in code: "+fixupBuf);
+                code.addFixups(fixupBuf);
+            }
+        }
+    }
+}