/* * Copyright 2001-2005 Sun Microsystems, Inc. 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. Sun designates this * particular file as subject to the "Classpath" exception as provided * by Sun 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 Sun Microsystems, Inc., 4150 Network Circle, Santa Clara, * CA 95054 USA or visit www.sun.com if you need additional information or * have any questions. */ package com.sun.java.util.jar.pack; import java.io.*; import java.util.*; import com.sun.java.util.jar.pack.Package.Class; import com.sun.java.util.jar.pack.Package.InnerClass; import com.sun.java.util.jar.pack.ConstantPool.*; /** * Reader for a class file that is being incorporated into a package. * @author John Rose */ class ClassReader implements Constants { int verbose; Package pkg; Class cls; long inPos; DataInputStream in; Map attrDefs; Map attrCommands; String unknownAttrCommand = "error";; ClassReader(Class cls, InputStream in) throws IOException { this.pkg = cls.getPackage(); this.cls = cls; this.verbose = pkg.verbose; this.in = new DataInputStream(new FilterInputStream(in) { public int read(byte b[], int off, int len) throws IOException { int nr = super.read(b, off, len); if (nr >= 0) inPos += nr; return nr; } public int read() throws IOException { int ch = super.read(); if (ch >= 0) inPos += 1; return ch; } public long skip(long n) throws IOException { long ns = super.skip(n); if (ns >= 0) inPos += ns; return ns; } }); } public void setAttrDefs(Map attrDefs) { this.attrDefs = attrDefs; } public void setAttrCommands(Map attrCommands) { this.attrCommands = attrCommands; } private void skip(int n, String what) throws IOException { Utils.log.warning("skipping "+n+" bytes of "+what); long skipped = 0; while (skipped < n) { long j = in.skip(n - skipped); assert(j > 0); skipped += j; } assert(skipped == n); } private int readUnsignedShort() throws IOException { return in.readUnsignedShort(); } private int readInt() throws IOException { return in.readInt(); } /** Read a 2-byte int, and return the <em>global</em> CP entry for it. */ private Entry readRef() throws IOException { int i = in.readUnsignedShort(); return i == 0 ? null : cls.cpMap[i]; } private Entry readRef(byte tag) throws IOException { Entry e = readRef(); assert(e != null); assert(e.tagMatches(tag)); return e; } private Entry readRefOrNull(byte tag) throws IOException { Entry e = readRef(); assert(e == null || e.tagMatches(tag)); return e; } private Utf8Entry readUtf8Ref() throws IOException { return (Utf8Entry) readRef(CONSTANT_Utf8); } private ClassEntry readClassRef() throws IOException { return (ClassEntry) readRef(CONSTANT_Class); } private ClassEntry readClassRefOrNull() throws IOException { return (ClassEntry) readRefOrNull(CONSTANT_Class); } private SignatureEntry readSignatureRef() throws IOException { // The class file stores a Utf8, but we want a Signature. Entry e = readRef(CONSTANT_Utf8); return ConstantPool.getSignatureEntry(e.stringValue()); } void read() throws IOException { boolean ok = false; try { readMagicNumbers(); readConstantPool(); readHeader(); readMembers(false); // fields readMembers(true); // methods readAttributes(ATTR_CONTEXT_CLASS, cls); cls.finishReading(); assert(0 >= in.read(new byte[1])); ok = true; } finally { if (!ok) { if (verbose > 0) Utils.log.warning("Erroneous data at input offset "+inPos+" of "+cls.file); } } } void readMagicNumbers() throws IOException { cls.magic = in.readInt(); if (cls.magic != JAVA_MAGIC) throw new Attribute.FormatException ("Bad magic number in class file " +Integer.toHexString(cls.magic), ATTR_CONTEXT_CLASS, "magic-number", "pass"); cls.minver = (short) readUnsignedShort(); cls.majver = (short) readUnsignedShort(); //System.out.println("ClassFile.version="+cls.majver+"."+cls.minver); String bad = checkVersion(cls.majver, cls.minver); if (bad != null) { throw new Attribute.FormatException ("classfile version too "+bad+": " +cls.majver+"."+cls.minver+" in "+cls.file, ATTR_CONTEXT_CLASS, "version", "pass"); } } private String checkVersion(int majver, int minver) { if (majver < pkg.min_class_majver || (majver == pkg.min_class_majver && minver < pkg.min_class_minver)) { return "small"; } if (majver > pkg.max_class_majver || (majver == pkg.max_class_majver && minver > pkg.max_class_minver)) { return "large"; } return null; // OK } void readConstantPool() throws IOException { int length = in.readUnsignedShort(); //System.err.println("reading CP, length="+length); int[] fixups = new int[length*4]; int fptr = 0; Entry[] cpMap = new Entry[length]; cpMap[0] = null; for (int i = 1; i < length; i++) { //System.err.println("reading CP elt, i="+i); int tag = in.readByte(); switch (tag) { case CONSTANT_Utf8: cpMap[i] = ConstantPool.getUtf8Entry(in.readUTF()); break; case CONSTANT_Integer: { Comparable val = new Integer(in.readInt()); cpMap[i] = ConstantPool.getLiteralEntry(val); } break; case CONSTANT_Float: { Comparable val = new Float(in.readFloat()); cpMap[i] = ConstantPool.getLiteralEntry(val); } break; case CONSTANT_Long: { Comparable val = new Long(in.readLong()); cpMap[i] = ConstantPool.getLiteralEntry(val); cpMap[++i] = null; } break; case CONSTANT_Double: { Comparable val = new Double(in.readDouble()); cpMap[i] = ConstantPool.getLiteralEntry(val); cpMap[++i] = null; } break; // just read the refs; do not attempt to resolve while reading case CONSTANT_Class: case CONSTANT_String: fixups[fptr++] = i; fixups[fptr++] = tag; fixups[fptr++] = in.readUnsignedShort(); fixups[fptr++] = -1; // empty ref2 break; case CONSTANT_Fieldref: case CONSTANT_Methodref: case CONSTANT_InterfaceMethodref: case CONSTANT_NameandType: fixups[fptr++] = i; fixups[fptr++] = tag; fixups[fptr++] = in.readUnsignedShort(); fixups[fptr++] = in.readUnsignedShort(); break; default: throw new IOException("Bad constant pool tag "+tag); } } // Fix up refs, which might be out of order. while (fptr > 0) { if (verbose > 3) Utils.log.fine("CP fixups ["+fptr/4+"]"); int flimit = fptr; fptr = 0; for (int fi = 0; fi < flimit; ) { int cpi = fixups[fi++]; int tag = fixups[fi++]; int ref = fixups[fi++]; int ref2 = fixups[fi++]; if (verbose > 3) Utils.log.fine(" cp["+cpi+"] = "+ConstantPool.tagName(tag)+"{"+ref+","+ref2+"}"); if (cpMap[ref] == null || ref2 >= 0 && cpMap[ref2] == null) { // Defer. fixups[fptr++] = cpi; fixups[fptr++] = tag; fixups[fptr++] = ref; fixups[fptr++] = ref2; continue; } switch (tag) { case CONSTANT_Class: cpMap[cpi] = ConstantPool.getClassEntry(cpMap[ref].stringValue()); break; case CONSTANT_String: cpMap[cpi] = ConstantPool.getStringEntry(cpMap[ref].stringValue()); break; case CONSTANT_Fieldref: case CONSTANT_Methodref: case CONSTANT_InterfaceMethodref: ClassEntry mclass = (ClassEntry) cpMap[ref]; DescriptorEntry mdescr = (DescriptorEntry) cpMap[ref2]; cpMap[cpi] = ConstantPool.getMemberEntry((byte)tag, mclass, mdescr); break; case CONSTANT_NameandType: Utf8Entry mname = (Utf8Entry) cpMap[ref]; Utf8Entry mtype = (Utf8Entry) cpMap[ref2]; cpMap[cpi] = ConstantPool.getDescriptorEntry(mname, mtype); break; default: assert(false); } } assert(fptr < flimit); // Must make progress. } cls.cpMap = cpMap; } void readHeader() throws IOException { cls.flags = readUnsignedShort(); cls.thisClass = readClassRef(); cls.superClass = readClassRefOrNull(); int ni = readUnsignedShort(); cls.interfaces = new ClassEntry[ni]; for (int i = 0; i < ni; i++) { cls.interfaces[i] = readClassRef(); } } void readMembers(boolean doMethods) throws IOException { int nm = readUnsignedShort(); for (int i = 0; i < nm; i++) { readMember(doMethods); } } void readMember(boolean doMethod) throws IOException { int mflags = readUnsignedShort(); Utf8Entry mname = readUtf8Ref(); SignatureEntry mtype = readSignatureRef(); DescriptorEntry descr = ConstantPool.getDescriptorEntry(mname, mtype); Class.Member m; if (!doMethod) m = cls.new Field(mflags, descr); else m = cls.new Method(mflags, descr); readAttributes(!doMethod ? ATTR_CONTEXT_FIELD : ATTR_CONTEXT_METHOD, m); } void readAttributes(int ctype, Attribute.Holder h) throws IOException { int na = readUnsignedShort(); if (na == 0) return; // nothing to do here if (verbose > 3) Utils.log.fine("readAttributes "+h+" ["+na+"]"); for (int i = 0; i < na; i++) { String name = readUtf8Ref().stringValue(); int length = readInt(); // See if there is a special command that applies. if (attrCommands != null) { Object lkey = Attribute.keyForLookup(ctype, name); String cmd = (String) attrCommands.get(lkey); if (cmd == "pass") { String message = "passing attribute bitwise in "+h; throw new Attribute.FormatException(message, ctype, name, cmd); } else if (cmd == "error") { String message = "attribute not allowed in "+h; throw new Attribute.FormatException(message, ctype, name, cmd); } else if (cmd == "strip") { skip(length, name+" attribute in "+h); continue; } } // Find canonical instance of the requested attribute. Attribute a = Attribute.lookup(Package.attrDefs, ctype, name); if (verbose > 4 && a != null) Utils.log.fine("pkg_attribute_lookup "+name+" = "+a); if (a == null) { a = Attribute.lookup(this.attrDefs, ctype, name); if (verbose > 4 && a != null) Utils.log.fine("this "+name+" = "+a); } if (a == null) { a = Attribute.lookup(null, ctype, name); if (verbose > 4 && a != null) Utils.log.fine("null_attribute_lookup "+name+" = "+a); } if (a == null && length == 0) { // Any zero-length attr is "known"... // We can assume an empty attr. has an empty layout. // Handles markers like Enum, Bridge, Synthetic, Deprecated. a = Attribute.find(ctype, name, ""); } boolean isStackMap = (ctype == ATTR_CONTEXT_CODE && (name.equals("StackMap") || name.equals("StackMapX"))); if (isStackMap) { // Known attribute but with a corner case format, "pass" it. Code code = (Code) h; final int TOO_BIG = 0x10000; if (code.max_stack >= TOO_BIG || code.max_locals >= TOO_BIG || code.getLength() >= TOO_BIG || name.endsWith("X")) { // No, we don't really know what to do this this one. // Do not compress the rare and strange "u4" and "X" cases. a = null; } } if (a == null) { if (isStackMap) { // Known attribute but w/o a format; pass it. String message = "unsupported StackMap variant in "+h; throw new Attribute.FormatException(message, ctype, name, "pass"); } else if (unknownAttrCommand == "strip") { // Skip the unknown attribute. skip(length, "unknown "+name+" attribute in "+h); continue; } else { String message = "unknown in "+h; throw new Attribute.FormatException(message, ctype, name, unknownAttrCommand); } } if (a.layout() == Package.attrCodeEmpty || a.layout() == Package.attrInnerClassesEmpty) { // These are hardwired. long pos0 = inPos; if (a.name() == "Code") { Class.Method m = (Class.Method) h; m.code = new Code(m); readCode(m.code); } else { assert(h == cls); readInnerClasses(cls); } assert(length == inPos - pos0); // Keep empty attribute a... } else if (length > 0) { byte[] bytes = new byte[length]; in.readFully(bytes); a = a.addContent(bytes); } h.addAttribute(a); if (verbose > 2) Utils.log.fine("read "+a); } } void readCode(Code code) throws IOException { code.max_stack = readUnsignedShort(); code.max_locals = readUnsignedShort(); code.bytes = new byte[readInt()]; in.readFully(code.bytes); int nh = readUnsignedShort(); code.setHandlerCount(nh); for (int i = 0; i < nh; i++) { code.handler_start[i] = readUnsignedShort(); code.handler_end[i] = readUnsignedShort(); code.handler_catch[i] = readUnsignedShort(); code.handler_class[i] = readClassRefOrNull(); } readAttributes(ATTR_CONTEXT_CODE, code); } void readInnerClasses(Class cls) throws IOException { int nc = readUnsignedShort(); ArrayList ics = new ArrayList(nc); for (int i = 0; i < nc; i++) { InnerClass ic = new InnerClass(readClassRef(), readClassRefOrNull(), (Utf8Entry)readRefOrNull(CONSTANT_Utf8), readUnsignedShort()); ics.add(ic); } cls.innerClasses = ics; // set directly; do not use setInnerClasses. // (Later, ics may be transferred to the pkg.) } }