| /* |
| * 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); |
| } |
| } |
| } |
| } |