/*
* This file is part of the Jikes RVM project (http://jikesrvm.org).
*
* This file is licensed to You under the Eclipse Public License (EPL);
* You may not use this file except in compliance with the License. You
* may obtain a copy of the License at
*
* http://www.opensource.org/licenses/eclipse-1.0.php
*
* See the COPYRIGHT.txt file distributed with this work for information
* regarding copyright ownership.
*/
package org.jikesrvm.classloader;
import static org.jikesrvm.VM.NOT_REACHED;
import static org.jikesrvm.classloader.ClassLoaderConstants.*;
import static org.jikesrvm.runtime.JavaSizeConstants.BITS_IN_SHORT;
import static org.jikesrvm.runtime.JavaSizeConstants.BYTES_IN_INT;
import static org.jikesrvm.runtime.JavaSizeConstants.BYTES_IN_LONG;
import static org.jikesrvm.runtime.UnboxedSizeConstants.BYTES_IN_ADDRESS;
import java.io.DataInputStream;
import java.io.IOException;
import org.jikesrvm.VM;
import org.jikesrvm.runtime.Statics;
import org.vmmagic.pragma.Uninterruptible;
import org.vmmagic.unboxed.Offset;
/**
* Support code to parse a DataInputStream in the Java classfile format
* and create the appropriate instance of an RVMClass or UnboxedType.
* Also low-level support for our internal constant pool format.
*/
public class ClassFileReader {
/**
* Parse and return the constant pool in a class file
* @param typeRef the canonical type reference for this type.
* @param input the data stream from which to read the class's description.
* @return constant pool as int array
* @throws IOException if it occurs during reading of the input stream
*/
static int[] readConstantPool(TypeReference typeRef, DataInputStream input) throws ClassFormatError, IOException {
int magic = input.readInt();
if (magic != 0xCAFEBABE) {
throw new ClassFormatError("bad magic number " + Integer.toHexString(magic));
}
// Get the class file version number and check to see if it is a version
// that we support.
int minor = input.readUnsignedShort();
int major = input.readUnsignedShort();
switch (major) {
case 45:
case 46:
case 47:
case 48:
case 49: // we support all variants of these major versions so the minor number doesn't matter.
break;
case 50: // we only support up to 50.0 (ie Java 1.6.0)
if (minor == 0) break;
default:
throw new UnsupportedClassVersionError("unsupported class file version " + major + "." + minor);
}
//
// pass 1: read constant pool
//
int[] constantPool = new int[input.readUnsignedShort()];
byte[] tmpTags = new byte[constantPool.length];
// note: slot 0 is unused
for (int i = 1; i < constantPool.length; i++) {
tmpTags[i] = input.readByte();
switch (tmpTags[i]) {
case TAG_UTF: {
byte[] utf = new byte[input.readUnsignedShort()];
input.readFully(utf);
int atomId = Atom.findOrCreateUtf8Atom(utf).getId();
constantPool[i] = packCPEntry(CP_UTF, atomId);
break;
}
case TAG_UNUSED:
if (VM.VerifyAssertions) VM._assert(NOT_REACHED);
break;
case TAG_INT: {
int literal = input.readInt();
int offset = Statics.findOrCreateIntSizeLiteral(literal);
constantPool[i] = packCPEntry(CP_INT, offset);
break;
}
case TAG_FLOAT: {
int literal = input.readInt();
int offset = Statics.findOrCreateIntSizeLiteral(literal);
constantPool[i] = packCPEntry(CP_FLOAT, offset);
break;
}
case TAG_LONG: {
long literal = input.readLong();
int offset = Statics.findOrCreateLongSizeLiteral(literal);
constantPool[i] = packCPEntry(CP_LONG, offset);
i++;
break;
}
case TAG_DOUBLE: {
long literal = input.readLong();
int offset = Statics.findOrCreateLongSizeLiteral(literal);
constantPool[i] = packCPEntry(CP_DOUBLE, offset);
i++;
break;
}
case TAG_TYPEREF:
constantPool[i] = input.readUnsignedShort();
break;
case TAG_STRING:
constantPool[i] = input.readUnsignedShort();
break;
case TAG_FIELDREF:
case TAG_METHODREF:
case TAG_INTERFACE_METHODREF: {
int classDescriptorIndex = input.readUnsignedShort();
int memberNameAndDescriptorIndex = input.readUnsignedShort();
constantPool[i] = packTempCPEntry(classDescriptorIndex, memberNameAndDescriptorIndex);
break;
}
case TAG_MEMBERNAME_AND_DESCRIPTOR: {
int memberNameIndex = input.readUnsignedShort();
int descriptorIndex = input.readUnsignedShort();
constantPool[i] = packTempCPEntry(memberNameIndex, descriptorIndex);
break;
}
default:
throw new ClassFormatError("bad constant pool");
}
}
//
// pass 2: post-process type and string constant pool entries
// (we must do this in a second pass because of forward references)
//
try {
for (int i = 1; i < constantPool.length; i++) {
switch (tmpTags[i]) {
case TAG_LONG:
case TAG_DOUBLE:
++i;
break;
case TAG_TYPEREF: { // in: utf index
Atom typeName = getUtf(constantPool, constantPool[i]);
int typeRefId =
TypeReference.findOrCreate(typeRef.getClassLoader(), typeName.descriptorFromClassName()).getId();
constantPool[i] = packCPEntry(CP_CLASS, typeRefId);
break;
} // out: type reference id
case TAG_STRING: { // in: utf index
Atom literal = getUtf(constantPool, constantPool[i]);
int offset = literal.getStringLiteralOffset();
constantPool[i] = packCPEntry(CP_STRING, offset);
break;
} // out: jtoc slot number
}
}
} catch (java.io.UTFDataFormatException x) {
ClassFormatError error = new ClassFormatError(x.toString());
error.initCause(x);
throw error;
}
//
// pass 3: post-process type field and method constant pool entries
//
for (int i = 1; i < constantPool.length; i++) {
switch (tmpTags[i]) {
case TAG_LONG:
case TAG_DOUBLE:
++i;
break;
case TAG_FIELDREF:
case TAG_METHODREF:
case TAG_INTERFACE_METHODREF: { // in: classname+membername+memberdescriptor indices
int bits = constantPool[i];
int classNameIndex = unpackTempCPIndex1(bits);
int memberNameAndDescriptorIndex = unpackTempCPIndex2(bits);
int memberNameAndDescriptorBits = constantPool[memberNameAndDescriptorIndex];
int memberNameIndex = unpackTempCPIndex1(memberNameAndDescriptorBits);
int memberDescriptorIndex = unpackTempCPIndex2(memberNameAndDescriptorBits);
TypeReference tref = getTypeRef(constantPool, classNameIndex);
Atom memberName = getUtf(constantPool, memberNameIndex);
Atom memberDescriptor = getUtf(constantPool, memberDescriptorIndex);
MemberReference mr = MemberReference.findOrCreate(tref, memberName, memberDescriptor);
int mrId = mr.getId();
constantPool[i] = packCPEntry(CP_MEMBER, mrId);
break;
} // out: MemberReference id
}
}
return constantPool;
}
/**
* Read the class' TypeReference
* @param typeRef the type reference that we're expecting to read
* @param input input stream
* @param constantPool constant pool
* @return the constantPool index of the typeRef of the class we are reading
* @throws IOException when a problems occurs while reading the input stream
* @throws ClassFormatError when the read type ref does not match up with the expected type ref
*/
static int readTypeRef(TypeReference typeRef, DataInputStream input, int[] constantPool) throws IOException, ClassFormatError {
int myTypeIndex = input.readUnsignedShort();
TypeReference myTypeRef = getTypeRef(constantPool, myTypeIndex);
if (myTypeRef != typeRef) {
// eg. file contains a different class than would be
// expected from its .class file name
if (!VM.VerifyAssertions) {
throw new ClassFormatError("expected class \"" +
typeRef.getName() +
"\" but found \"" +
myTypeRef.getName() +
"\"");
} else {
throw new ClassFormatError("expected class \"" +
typeRef.getName() +
"\" but found \"" +
myTypeRef.getName() +
"\"\n" + typeRef + " != " + myTypeRef);
}
}
return myTypeIndex;
}
static RVMClass readSuperClass(DataInputStream input, int[] constantPool,
short modifiers) throws IOException, NoClassDefFoundError {
TypeReference superType = getTypeRef(constantPool, input.readUnsignedShort()); // possibly null
RVMClass superClass = null;
if (((modifiers & ACC_INTERFACE) == 0) && (superType != null)) {
superClass = superType.resolve().asClass();
}
return superClass;
}
static RVMClass[] readDeclaredInterfaces(DataInputStream input, int[] constantPool) throws IOException, NoClassDefFoundError {
int numInterfaces = input.readUnsignedShort();
RVMClass[] declaredInterfaces;
if (numInterfaces == 0) {
declaredInterfaces = RVMType.emptyVMClass;
} else {
declaredInterfaces = new RVMClass[numInterfaces];
for (int i = 0; i < numInterfaces; ++i) {
TypeReference inTR = getTypeRef(constantPool, input.readUnsignedShort());
declaredInterfaces[i] = inTR.resolve().asClass();
}
}
return declaredInterfaces;
}
static RVMField[] readDeclaredFields(TypeReference typeRef, DataInputStream input, int[] constantPool) throws IOException {
int numFields = input.readUnsignedShort();
RVMField[] declaredFields;
if (numFields == 0) {
declaredFields = RVMType.emptyVMField;
} else {
declaredFields = new RVMField[numFields];
for (int i = 0; i < numFields; i++) {
short fmodifiers = input.readShort();
Atom fieldName = getUtf(constantPool, input.readUnsignedShort());
Atom fieldDescriptor = getUtf(constantPool, input.readUnsignedShort());
if (typeRef == TypeReference.JavaLangSystem &&
(fmodifiers & (ACC_STATIC | ACC_FINAL | ACC_PUBLIC)) == (ACC_STATIC | ACC_FINAL | ACC_PUBLIC)) {
/* We have to stop System.in .out and .err fields from being final! */
fmodifiers -= ACC_FINAL;
}
MemberReference memRef = MemberReference.findOrCreate(typeRef, fieldName, fieldDescriptor);
declaredFields[i] = RVMField.readField(typeRef, constantPool, memRef, fmodifiers, input);
}
}
return declaredFields;
}
static RVMMethod[] readDeclaredMethods(TypeReference typeRef, DataInputStream input, int[] constantPool) throws IOException {
int numMethods = input.readUnsignedShort();
RVMMethod[] declaredMethods;
if (numMethods == 0) {
declaredMethods = RVMType.emptyVMMethod;
} else {
declaredMethods = new RVMMethod[numMethods];
for (int i = 0; i < numMethods; i++) {
short mmodifiers = input.readShort();
Atom methodName = getUtf(constantPool, input.readUnsignedShort());
Atom methodDescriptor = getUtf(constantPool, input.readUnsignedShort());
MemberReference memRef = MemberReference.findOrCreate(typeRef, methodName, methodDescriptor);
RVMMethod method = RVMMethod.readMethod(typeRef, constantPool, memRef, mmodifiers, input);
declaredMethods[i] = method;
}
}
return declaredMethods;
}
/**
* Returns the class initializer method among the declared methods of the class
* @param declaredMethods the methods declared by the class
* @return the class initializer method {@code <clinit>} of the class or {@code null}
* if none was found
*/
static RVMMethod getClassInitializerMethod(RVMMethod[] declaredMethods) {
for (RVMMethod method : declaredMethods) {
if (method.isClassInitializer()) return method;
}
return null;
}
/**
* Creates an instance of a RVMClass.
* @param typeRef the canonical type reference for this type.
* @param input the data stream from which to read the class's description.
* @return a newly created class
* @throws IOException when data cannot be read from the input stream or
* skipping during class construction reads less data than expected
* @throws ClassFormatError when the class data is corrupt
*/
static RVMClass readClass(TypeReference typeRef, DataInputStream input) throws ClassFormatError, IOException {
if (RVMClass.isClassLoadingDisabled()) {
throw new RuntimeException("ClassLoading Disabled : " + typeRef);
}
if (VM.TraceClassLoading && VM.runningVM) {
VM.sysWriteln("RVMClass: (begin) load file " + typeRef.getName());
}
int[] constantPool = readConstantPool(typeRef, input);
short modifiers = input.readShort();
short originalModifiers = modifiers;
int myTypeIndex = readTypeRef(typeRef, input, constantPool);
RVMClass superClass = readSuperClass(input, constantPool, modifiers);
RVMClass[] declaredInterfaces = readDeclaredInterfaces(input, constantPool);
RVMField[] declaredFields = readDeclaredFields(typeRef, input, constantPool);
RVMMethod[] declaredMethods = readDeclaredMethods(typeRef, input, constantPool);
RVMMethod classInitializerMethod = getClassInitializerMethod(declaredMethods);
TypeReference[] declaredClasses = null;
Atom sourceName = null;
TypeReference declaringClass = null;
Atom signature = null;
RVMAnnotation[] annotations = null;
TypeReference enclosingClass = null;
MethodReference enclosingMethod = null;
// Read attributes.
for (int i = 0, n = input.readUnsignedShort(); i < n; ++i) {
Atom attName = getUtf(constantPool, input.readUnsignedShort());
int attLength = input.readInt();
// Class attributes
if (attName == RVMClassLoader.sourceFileAttributeName && attLength == 2) {
sourceName = getUtf(constantPool, input.readUnsignedShort());
} else if (attName == RVMClassLoader.innerClassesAttributeName) {
// Parse InnerClasses attribute, and use the information to populate
// the list of declared member classes. We do this so we can
// support the java.lang.Class.getDeclaredClasses()
// and java.lang.Class.getDeclaredClass methods.
int numberOfClasses = input.readUnsignedShort();
declaredClasses = new TypeReference[numberOfClasses];
for (int j = 0; j < numberOfClasses; ++j) {
int innerClassInfoIndex = input.readUnsignedShort();
int outerClassInfoIndex = input.readUnsignedShort();
int innerNameIndex = input.readUnsignedShort();
int innerClassAccessFlags = input.readUnsignedShort();
if (innerClassInfoIndex != 0 && outerClassInfoIndex == myTypeIndex && innerNameIndex != 0) {
// This looks like a declared inner class.
declaredClasses[j] = getTypeRef(constantPool, innerClassInfoIndex);
}
if (innerClassInfoIndex == myTypeIndex) {
if (outerClassInfoIndex != 0) {
declaringClass = getTypeRef(constantPool, outerClassInfoIndex);
if (enclosingClass == null) {
// TODO: is this the null test necessary?
enclosingClass = declaringClass;
}
}
if ((innerClassAccessFlags & (ACC_PRIVATE | ACC_PROTECTED)) != 0) {
modifiers &= ~(ACC_PUBLIC | ACC_PRIVATE | ACC_PROTECTED);
}
modifiers |= innerClassAccessFlags;
originalModifiers = (short) innerClassAccessFlags;
}
}
} else if (attName == RVMClassLoader.syntheticAttributeName) {
modifiers |= ACC_SYNTHETIC;
} else if (attName == RVMClassLoader.enclosingMethodAttributeName) {
int enclosingClassIndex = input.readUnsignedShort();
enclosingClass = getTypeRef(constantPool, enclosingClassIndex);
int enclosingMethodIndex = input.readUnsignedShort();
if (enclosingMethodIndex != 0) {
int memberNameIndex = constantPool[enclosingMethodIndex] >>> BITS_IN_SHORT;
int memberDescriptorIndex = constantPool[enclosingMethodIndex] & ((1 << BITS_IN_SHORT) - 1);
Atom memberName = getUtf(constantPool, memberNameIndex);
Atom memberDescriptor = getUtf(constantPool, memberDescriptorIndex);
enclosingMethod =
MemberReference.findOrCreate(enclosingClass, memberName, memberDescriptor).asMethodReference();
}
} else if (attName == RVMClassLoader.signatureAttributeName) {
signature = getUtf(constantPool, input.readUnsignedShort());
} else if (attName == RVMClassLoader.runtimeVisibleAnnotationsAttributeName) {
annotations = AnnotatedElement.readAnnotations(constantPool, input, typeRef.getClassLoader());
} else {
int skippedAmount = input.skipBytes(attLength);
if (skippedAmount != attLength) {
throw new IOException("Unexpected short skip");
}
}
}
return new RVMClass(typeRef,
constantPool,
modifiers,
originalModifiers,
superClass,
declaredInterfaces,
declaredFields,
declaredMethods,
declaredClasses,
declaringClass,
enclosingClass,
enclosingMethod,
sourceName,
classInitializerMethod,
signature,
annotations);
}
@Uninterruptible
static int packCPEntry(byte type, int value) {
return (type << 29) | (value & 0x1fffffff);
}
@Uninterruptible
static byte unpackCPType(int cpValue) {
return (byte) (cpValue >>> 29);
}
@Uninterruptible
static int unpackSignedCPValue(int cpValue) {
return (cpValue << 3) >> 3;
}
@Uninterruptible
static int unpackUnsignedCPValue(int cpValue) {
return cpValue & 0x1fffffff;
}
@Uninterruptible
static boolean packedCPTypeIsClassType(int cpValue) {
return (cpValue & (7 << 29)) == (CP_CLASS << 29);
}
@Uninterruptible
static int packTempCPEntry(int index1, int index2) {
return (index1 << 16) | (index2 & 0xffff);
}
@Uninterruptible
static int unpackTempCPIndex1(int cpValue) {
return cpValue >>> 16;
}
@Uninterruptible
static int unpackTempCPIndex2(int cpValue) {
return cpValue & 0xffff;
}
static int getLiteralSize(int[] constantPool, int constantPoolIndex) {
int cpValue = constantPool[constantPoolIndex];
switch (unpackCPType(cpValue)) {
case CP_INT:
case CP_FLOAT:
return BYTES_IN_INT;
case CP_LONG:
case CP_DOUBLE:
return BYTES_IN_LONG;
case CP_CLASS:
case CP_STRING:
return BYTES_IN_ADDRESS;
default:
if (VM.VerifyAssertions) VM._assert(NOT_REACHED);
return 0;
}
}
/**
* Get offset of a literal constant, in bytes.
* Offset is with respect to virtual machine's "table of contents" (jtoc).
*
* @param constantPool the constant pool
* @param constantPoolIndex the index into the constant pool
* @return the offset in bytes from the JTOC
*/
static Offset getLiteralOffset(int[] constantPool, int constantPoolIndex) {
int cpValue = constantPool[constantPoolIndex];
if (VM.VerifyAssertions) {
int value = unpackSignedCPValue(cpValue);
byte type = unpackCPType(cpValue);
switch (type) {
case CP_INT:
case CP_FLOAT:
case CP_LONG:
case CP_DOUBLE:
case CP_STRING:
return Offset.fromIntSignExtend(value);
case CP_CLASS: {
int typeId = unpackUnsignedCPValue(cpValue);
Class<?> literalAsClass = TypeReference.getTypeRef(typeId).resolve().getClassForType();
return Offset.fromIntSignExtend(Statics.findOrCreateObjectLiteral(literalAsClass));
}
default:
VM._assert(NOT_REACHED);
return Offset.fromIntSignExtend(0xebad0ff5);
}
} else {
if (packedCPTypeIsClassType(cpValue)) {
int typeId = unpackUnsignedCPValue(cpValue);
Class<?> literalAsClass = TypeReference.getTypeRef(typeId).resolve().getClassForType();
return Offset.fromIntSignExtend(Statics.findOrCreateObjectLiteral(literalAsClass));
} else {
int value = unpackSignedCPValue(cpValue);
return Offset.fromIntSignExtend(value);
}
}
}
static byte getLiteralDescription(int[] constantPool, int constantPoolIndex) {
int cpValue = constantPool[constantPoolIndex];
byte type = unpackCPType(cpValue);
return type;
}
@Uninterruptible
static TypeReference getTypeRef(int[] constantPool, int constantPoolIndex) {
if (constantPoolIndex != 0) {
int cpValue = constantPool[constantPoolIndex];
if (VM.VerifyAssertions) VM._assert(unpackCPType(cpValue) == CP_CLASS);
return TypeReference.getTypeRef(unpackUnsignedCPValue(cpValue));
} else {
return null;
}
}
@Uninterruptible
static MethodReference getMethodRef(int[] constantPool, int constantPoolIndex) {
int cpValue = constantPool[constantPoolIndex];
if (VM.VerifyAssertions) VM._assert(unpackCPType(cpValue) == CP_MEMBER);
return (MethodReference) MemberReference.getMemberRef(unpackUnsignedCPValue(cpValue));
}
@Uninterruptible
static FieldReference getFieldRef(int[] constantPool, int constantPoolIndex) {
int cpValue = constantPool[constantPoolIndex];
if (VM.VerifyAssertions) VM._assert(unpackCPType(cpValue) == CP_MEMBER);
return (FieldReference) MemberReference.getMemberRef(unpackUnsignedCPValue(cpValue));
}
@Uninterruptible
static Atom getUtf(int[] constantPool, int constantPoolIndex) {
int cpValue = constantPool[constantPoolIndex];
if (VM.VerifyAssertions) VM._assert(unpackCPType(cpValue) == CP_UTF);
return Atom.getAtom(unpackUnsignedCPValue(cpValue));
}
}