blob: 3da159f6e29033716cd7a5a48ecbfcb2a64b818c [file] [log] [blame]
/*
* Copyright (c) 2001, 2013, 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.ClassEntry;
import com.sun.java.util.jar.pack.ConstantPool.DescriptorEntry;
import com.sun.java.util.jar.pack.ConstantPool.Entry;
import com.sun.java.util.jar.pack.ConstantPool.SignatureEntry;
import com.sun.java.util.jar.pack.ConstantPool.MemberEntry;
import com.sun.java.util.jar.pack.ConstantPool.MethodHandleEntry;
import com.sun.java.util.jar.pack.ConstantPool.BootstrapMethodEntry;
import com.sun.java.util.jar.pack.ConstantPool.Utf8Entry;
import com.sun.java.util.jar.pack.Package.Class;
import com.sun.java.util.jar.pack.Package.InnerClass;
import java.io.DataInputStream;
import java.io.FilterInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Map;
import static com.sun.java.util.jar.pack.Constants.*;
/**
* Reader for a class file that is being incorporated into a package.
* @author John Rose
*/
class ClassReader {
int verbose;
Package pkg;
Class cls;
long inPos;
long constantPoolLimit = -1;
DataInputStream in;
Map<Attribute.Layout, Attribute> attrDefs;
Map<Attribute.Layout, String> 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<Attribute.Layout, Attribute> attrDefs) {
this.attrDefs = attrDefs;
}
public void setAttrCommands(Map<Attribute.Layout, String> 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 instanceof UnresolvedEntry));
checkTag(e, tag);
return e;
}
/** Throw a ClassFormatException if the entry does not match the expected tag type. */
private Entry checkTag(Entry e, byte tag) throws ClassFormatException {
if (e == null || !e.tagMatches(tag)) {
String where = (inPos == constantPoolLimit
? " in constant pool"
: " at pos: " + inPos);
String got = (e == null
? "null CP index"
: "type=" + ConstantPool.tagName(e.tag));
throw new ClassFormatException("Bad constant, expected type=" +
ConstantPool.tagName(tag) +
" got "+ got + ", in File: " + cls.file.nameString + where);
}
return e;
}
private Entry checkTag(Entry e, byte tag, boolean nullOK) throws ClassFormatException {
return nullOK && e == null ? null : checkTag(e, tag);
}
private Entry readRefOrNull(byte tag) throws IOException {
Entry e = readRef();
checkTag(e, tag, true);
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_Signature);
return (e != null && e.getTag() == CONSTANT_Utf8)
? ConstantPool.getSignatureEntry(e.stringValue())
: (SignatureEntry) e;
}
void read() throws IOException {
boolean ok = false;
try {
readMagicNumbers();
readConstantPool();
readHeader();
readMembers(false); // fields
readMembers(true); // methods
readAttributes(ATTR_CONTEXT_CLASS, cls);
fixUnresolvedEntries();
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");
int minver = (short) readUnsignedShort();
int majver = (short) readUnsignedShort();
cls.version = Package.Version.of(majver, minver);
//System.out.println("ClassFile.version="+cls.majver+"."+cls.minver);
String bad = checkVersion(cls.version);
if (bad != null) {
throw new Attribute.FormatException
("classfile version too "+bad+": "
+cls.version+" in "+cls.file,
ATTR_CONTEXT_CLASS, "version", "pass");
}
}
private String checkVersion(Package.Version ver) {
int majver = ver.major;
int minver = ver.minor;
if (majver < pkg.minClassVersion.major ||
(majver == pkg.minClassVersion.major &&
minver < pkg.minClassVersion.minor)) {
return "small";
}
if (majver > pkg.maxClassVersion.major ||
(majver == pkg.maxClassVersion.major &&
minver > pkg.maxClassVersion.minor)) {
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:
{
cpMap[i] = ConstantPool.getLiteralEntry(in.readInt());
}
break;
case CONSTANT_Float:
{
cpMap[i] = ConstantPool.getLiteralEntry(in.readFloat());
}
break;
case CONSTANT_Long:
{
cpMap[i] = ConstantPool.getLiteralEntry(in.readLong());
cpMap[++i] = null;
}
break;
case CONSTANT_Double:
{
cpMap[i] = ConstantPool.getLiteralEntry(in.readDouble());
cpMap[++i] = null;
}
break;
// just read the refs; do not attempt to resolve while reading
case CONSTANT_Class:
case CONSTANT_String:
case CONSTANT_MethodType:
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;
case CONSTANT_InvokeDynamic:
fixups[fptr++] = i;
fixups[fptr++] = tag;
fixups[fptr++] = -1 ^ in.readUnsignedShort(); // not a ref
fixups[fptr++] = in.readUnsignedShort();
break;
case CONSTANT_MethodHandle:
fixups[fptr++] = i;
fixups[fptr++] = tag;
fixups[fptr++] = -1 ^ in.readUnsignedByte();
fixups[fptr++] = in.readUnsignedShort();
break;
default:
throw new ClassFormatException("Bad constant pool tag " +
tag + " in File: " + cls.file.nameString +
" at pos: " + inPos);
}
}
constantPoolLimit = inPos;
// 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 (ref >= 0 && 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) checkTag(cpMap[ref], CONSTANT_Class);
DescriptorEntry mdescr = (DescriptorEntry) checkTag(cpMap[ref2], CONSTANT_NameandType);
cpMap[cpi] = ConstantPool.getMemberEntry((byte)tag, mclass, mdescr);
break;
case CONSTANT_NameandType:
Utf8Entry mname = (Utf8Entry) checkTag(cpMap[ref], CONSTANT_Utf8);
Utf8Entry mtype = (Utf8Entry) checkTag(cpMap[ref2], CONSTANT_Signature);
cpMap[cpi] = ConstantPool.getDescriptorEntry(mname, mtype);
break;
case CONSTANT_MethodType:
cpMap[cpi] = ConstantPool.getMethodTypeEntry((Utf8Entry) checkTag(cpMap[ref], CONSTANT_Signature));
break;
case CONSTANT_MethodHandle:
byte refKind = (byte)(-1 ^ ref);
MemberEntry memRef = (MemberEntry) checkTag(cpMap[ref2], CONSTANT_AnyMember);
cpMap[cpi] = ConstantPool.getMethodHandleEntry(refKind, memRef);
break;
case CONSTANT_InvokeDynamic:
DescriptorEntry idescr = (DescriptorEntry) checkTag(cpMap[ref2], CONSTANT_NameandType);
cpMap[cpi] = new UnresolvedEntry((byte)tag, (-1 ^ ref), idescr);
// Note that ref must be resolved later, using the BootstrapMethods attribute.
break;
default:
assert(false);
}
}
assert(fptr < flimit); // Must make progress.
}
cls.cpMap = cpMap;
}
private /*non-static*/
class UnresolvedEntry extends Entry {
final Object[] refsOrIndexes;
UnresolvedEntry(byte tag, Object... refsOrIndexes) {
super(tag);
this.refsOrIndexes = refsOrIndexes;
ClassReader.this.haveUnresolvedEntry = true;
}
Entry resolve() {
Class cls = ClassReader.this.cls;
Entry res;
switch (tag) {
case CONSTANT_InvokeDynamic:
BootstrapMethodEntry iboots = cls.bootstrapMethods.get((Integer) refsOrIndexes[0]);
DescriptorEntry idescr = (DescriptorEntry) refsOrIndexes[1];
res = ConstantPool.getInvokeDynamicEntry(iboots, idescr);
break;
default:
throw new AssertionError();
}
return res;
}
private void unresolved() { throw new RuntimeException("unresolved entry has no string"); }
public int compareTo(Object x) { unresolved(); return 0; }
public boolean equals(Object x) { unresolved(); return false; }
protected int computeValueHash() { unresolved(); return 0; }
public String stringValue() { unresolved(); return toString(); }
public String toString() { return "(unresolved "+ConstantPool.tagName(tag)+")"; }
}
boolean haveUnresolvedEntry;
private void fixUnresolvedEntries() {
if (!haveUnresolvedEntry) return;
Entry[] cpMap = cls.getCPMap();
for (int i = 0; i < cpMap.length; i++) {
Entry e = cpMap[i];
if (e instanceof UnresolvedEntry) {
cpMap[i] = e = ((UnresolvedEntry)e).resolve();
assert(!(e instanceof UnresolvedEntry));
}
}
haveUnresolvedEntry = false;
}
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) {
Attribute.Layout lkey = Attribute.keyForLookup(ctype, name);
String cmd = attrCommands.get(lkey);
if (cmd != null) {
switch (cmd) {
case "pass":
String message1 = "passing attribute bitwise in " + h;
throw new Attribute.FormatException(message1, ctype, name, cmd);
case "error":
String message2 = "attribute not allowed in " + h;
throw new Attribute.FormatException(message2, ctype, name, cmd);
case "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 ("strip".equals(unknownAttrCommand)) {
// Skip the unknown attribute.
skip(length, "unknown "+name+" attribute in "+h);
continue;
} else {
String message = " is unknown attribute in class " + h;
throw new Attribute.FormatException(message, ctype, name,
unknownAttrCommand);
}
}
long pos0 = inPos; // in case we want to check it
if (a.layout() == Package.attrCodeEmpty) {
// These are hardwired.
Class.Method m = (Class.Method) h;
m.code = new Code(m);
try {
readCode(m.code);
} catch (Instruction.FormatException iie) {
String message = iie.getMessage() + " in " + h;
throw new ClassReader.ClassFormatException(message, iie);
}
assert(length == inPos - pos0);
// Keep empty attribute a...
} else if (a.layout() == Package.attrBootstrapMethodsEmpty) {
assert(h == cls);
readBootstrapMethods(cls);
assert(length == inPos - pos0);
// Delete the attribute; it is logically part of the constant pool.
continue;
} else if (a.layout() == Package.attrInnerClassesEmpty) {
// These are hardwired also.
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);
}
if (a.size() == 0 && !a.layout().isEmpty()) {
throw new ClassFormatException(name +
": attribute length cannot be zero, in " + h);
}
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);
Entry[] cpMap = cls.getCPMap();
Instruction.opcodeChecker(code.bytes, cpMap, this.cls.version);
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 readBootstrapMethods(Class cls) throws IOException {
BootstrapMethodEntry[] bsms = new BootstrapMethodEntry[readUnsignedShort()];
for (int i = 0; i < bsms.length; i++) {
MethodHandleEntry bsmRef = (MethodHandleEntry) readRef(CONSTANT_MethodHandle);
Entry[] argRefs = new Entry[readUnsignedShort()];
for (int j = 0; j < argRefs.length; j++) {
argRefs[j] = readRef(CONSTANT_LoadableValue);
}
bsms[i] = ConstantPool.getBootstrapMethodEntry(bsmRef, argRefs);
}
cls.setBootstrapMethods(Arrays.asList(bsms));
}
void readInnerClasses(Class cls) throws IOException {
int nc = readUnsignedShort();
ArrayList<InnerClass> 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.)
}
static class ClassFormatException extends IOException {
private static final long serialVersionUID = -3564121733989501833L;
public ClassFormatException(String message) {
super(message);
}
public ClassFormatException(String message, Throwable cause) {
super(message, cause);
}
}
}