/*
* Copyright (c) 2007, 2012, 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.
*
* 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.max.vm.classfile;
import static com.sun.max.annotate.LOCAL_SUBSTITUTION.Static.*;
import static com.sun.max.vm.VMOptions.*;
import static com.sun.max.vm.actor.Actor.*;
import static com.sun.max.vm.actor.holder.ClassActorFactory.*;
import static com.sun.max.vm.actor.member.MethodActor.*;
import static com.sun.max.vm.classfile.ErrorContext.*;
import static com.sun.max.vm.type.ClassRegistry.Property.*;
import static com.sun.max.vm.type.JavaTypeDescriptor.*;
import java.io.*;
import java.lang.annotation.*;
import java.lang.instrument.*;
import java.security.*;
import java.util.*;
import java.util.concurrent.*;
import java.util.jar.*;
import java.util.zip.*;
import com.sun.max.annotate.*;
import com.sun.max.collect.*;
import com.sun.max.lang.*;
import com.sun.max.platform.*;
import com.sun.max.program.*;
import com.sun.max.vm.*;
import com.sun.max.vm.actor.*;
import com.sun.max.vm.actor.holder.*;
import com.sun.max.vm.actor.member.*;
import com.sun.max.vm.classfile.ClassfileWriter.MaxineFlags;
import com.sun.max.vm.classfile.constant.*;
import com.sun.max.vm.hosted.*;
import com.sun.max.vm.instrument.*;
import com.sun.max.vm.intrinsics.*;
import com.sun.max.vm.jdk.*;
import com.sun.max.vm.runtime.*;
import com.sun.max.vm.ti.*;
import com.sun.max.vm.type.*;
import com.sun.max.vm.type.ClassRegistry.Property;
import com.sun.max.vm.value.*;
/**
* Reads a class file to create a corresponding {@link ClassActor}.
*/
public final class ClassfileReader {
public static final char JAVA_MIN_SUPPORTED_VERSION = 45;
public static final char JAVA_1_5_VERSION = 49;
public static final char JAVA_6_VERSION = 50;
public static final char JAVA_MAX_SUPPORTED_VERSION = 51;
public static final char JAVA_MAX_SUPPORTED_MINOR_VERSION = 0;
protected final ClassfileStream classfileStream;
protected final ClassLoader classLoader;
protected final ClassRegistry classRegistry;
protected ConstantPool constantPool;
protected int majorVersion;
protected TypeDescriptor classOuterClass;
protected TypeDescriptor[] classInnerClasses;
protected TypeDescriptor classDescriptor;
protected int classFlags;
public ClassfileReader(ClassfileStream classfileStream, ClassLoader classLoader) {
this.classfileStream = classfileStream;
this.classLoader = classLoader;
this.classRegistry = ClassRegistry.makeRegistry(classLoader);
}
/**
* A utility class for efficiently determining that a sequence of fields or methods
* are unique with respect to their names and signatures.
*/
static class MemberSet extends ChainedHashMapping<MemberActor, MemberActor> {
public MemberSet(int initialCapacity) {
super(initialCapacity);
}
@Override
public boolean equivalent(MemberActor memberActor1, MemberActor memberActor2) {
return memberActor1.matchesNameAndType(memberActor2.name, memberActor2.descriptor);
}
@Override
public int hashCode(MemberActor memberActor) {
return memberActor.name.hashCode() ^ memberActor.descriptor.hashCode();
}
/**
* Adds a given member to this set.
*
* @param memberActor the member to add
* @return the member actor currently in this set whose name and signature match {@code memberActor}. This will
* be null if there is no matching member currently in this set.
*/
public MemberActor add(MemberActor memberActor) {
return super.put(memberActor, memberActor);
}
}
/**
* Parses a Java identifier at a given offset of a string.
*
* @param string the string to test
* @param offset the offset at which a legal field or type identifier should occur
* @return the first character after the legal identifier or -1 if there is no legal identifier at {@code offset}
*/
public static int parseIdentifier(String string, int offset) {
boolean isFirstChar = true;
char ch;
int i;
for (i = offset; i != string.length(); i++, isFirstChar = false) {
ch = string.charAt(i);
if (ch < 128) {
// Quick check for ASCII
if ((ch >= 'a' && ch <= 'z') ||
(ch >= 'A' && ch <= 'Z') ||
(ch == '_' || ch == '$') ||
(!isFirstChar && ch >= '0' && ch <= '9')) {
continue;
}
break;
}
if (Character.isJavaIdentifierStart(ch)) {
continue;
}
if (!isFirstChar && Character.isJavaIdentifierPart(ch)) {
continue;
}
break;
}
return isFirstChar ? -1 : i;
}
public static void verifyFieldName(Utf8Constant name) {
if (!isValidFieldName(name)) {
throw classFormatError("Invalid field name: " + name);
}
}
public static void verifyMethodName(Utf8Constant name, boolean allowClinit) {
if (!isValidMethodName(name, allowClinit)) {
throw classFormatError("Invalid method name: " + name);
}
}
public static boolean isValidFieldName(Utf8Constant name) {
return parseIdentifier(name.string, 0) == name.string.length();
}
public static boolean isValidMethodName(Utf8Constant name, boolean allowClinit) {
if (name.equals(SymbolTable.INIT)) {
return true;
}
if (allowClinit && name.equals(SymbolTable.CLINIT)) {
return true;
}
return parseIdentifier(name.string, 0) == name.string.length();
}
/**
* Verifies that the class file version is supported.
*
* @param major the major version number
* @param minor the minor version number
*/
public static void verifyVersion(int major, int minor) {
if ((major >= JAVA_MIN_SUPPORTED_VERSION) &&
(major <= JAVA_MAX_SUPPORTED_VERSION) &&
((major != JAVA_MAX_SUPPORTED_VERSION) ||
(minor <= JAVA_MAX_SUPPORTED_MINOR_VERSION))) {
return;
}
throw classFormatError("Unsupported major.minor version " + major + "." + minor);
}
/**
* Verifies that the flags for a class are valid.
*
* @param flags the flags to test
* @return the flags the VM should use
* @throws ClassFormatError if the flags are invalid
*/
public static int verifyClassFlags(int flags, int major) {
boolean valid;
final int finalAndAbstract = ACC_FINAL | ACC_ABSTRACT;
if ((flags & ACC_INTERFACE) != 0) {
final int newFlags;
if (major < JAVA_6_VERSION) {
// Set abstract bit for old class files for backward compatibility
newFlags = flags | ACC_ABSTRACT;
} else {
newFlags = flags;
}
// If the ACC_INTERFACE flag of this class file is set, its ACC_ABSTRACT flag must also be set (2.13.1) and
// its ACC_PUBLIC flag may be set. Such a class file may not have any of the other flags in Table 4.1 set.
valid = (newFlags & (finalAndAbstract | ACC_SUPER)) == ACC_ABSTRACT;
} else {
// If the ACC_INTERFACE flag of this class file is not set, it may have any of the other flags in Table 4.1
// set. However, such a class file cannot have both its ACC_FINAL and ACC_ABSTRACT flags set (2.8.2).
valid = (flags & finalAndAbstract) != finalAndAbstract;
}
if (valid) {
return flags;
}
throw classFormatError("Invalid class flags 0x" + Integer.toHexString(flags));
}
/**
* Verifies that the flags for a field are valid.
*
* @param name the name of the field
* @param flags the flags to test
* @param isInterface if the field flags are being tested for an interface
* @throws ClassFormatError if the flags are invalid
*/
public static void verifyFieldFlags(String name, int flags, boolean isInterface) {
boolean valid;
if (!isInterface) {
// Class or instance fields
final int maskedFlags = flags & (ACC_PUBLIC | ACC_PRIVATE | ACC_PROTECTED);
// Make sure that flags has at most one of its ACC_PRIVATE,
// ACC_PROTECTED bits set. That is, do a population count of these
// bit positions corresponding to these flags and ensure that it is
// at most 1.
valid = maskedFlags == 0 || (maskedFlags & ~(maskedFlags - 1)) == maskedFlags;
// A field can't be both final and volatile
final int finalAndVolatile = ACC_FINAL | ACC_VOLATILE;
valid = valid && ((flags & finalAndVolatile) != finalAndVolatile);
} else {
// interface fields must be public static final (i.e. constants), but may have ACC_SYNTHETIC set
valid = (flags & ~ACC_SYNTHETIC) == (ACC_STATIC | ACC_FINAL | ACC_PUBLIC);
}
if (!valid) {
throw classFormatError(name + ": invalid field flags 0x" + Integer.toHexString(flags));
}
}
/**
* Verifies that the flags for a method are valid.
*
* @param flags the flags to test
* @param isInit true if the method is "<init>"
* @param isClinit true if the method is "<clinit>"
* @param majorVersion the version of the class file
* @throws ClassFormatError if the flags are invalid
*/
public static void verifyMethodFlags(int flags, boolean isInterface, boolean isInit, boolean isClinit, int majorVersion) {
// Class and interface initialization methods (3.9) are called
// implicitly by the Java virtual machine; the value of their
// access_flags item is ignored except for the settings of the
// ACC_STRICT flag.
if (isClinit) {
return;
}
// These are all small bits. The value is between 0 and 7.
final int maskedFlags = flags & (ACC_PUBLIC | ACC_PRIVATE | ACC_PROTECTED);
// Make sure that flags has at most one of its ACC_PRIVATE,
// ACC_PROTECTED bits set. That is, do a population count of these
// bit positions corresponding to these flags and ensure that it is
// at most 1.
boolean valid = maskedFlags == 0 || (maskedFlags & ~(maskedFlags - 1)) == maskedFlags;
if (valid) {
if (!isInterface) {
// class or instance methods
if ((flags & ACC_ABSTRACT) != 0) {
if ((flags & (ACC_FINAL | ACC_NATIVE | ACC_PRIVATE | ACC_STATIC)) != 0) {
valid = false;
}
if (majorVersion >= JAVA_1_5_VERSION) {
if ((flags & (ACC_SYNCHRONIZED | ACC_STRICT)) != 0) {
valid = false;
}
}
}
} else {
final int publicAndAbstract = ACC_ABSTRACT | ACC_PUBLIC;
if ((flags & publicAndAbstract) != publicAndAbstract) {
valid = false;
} else {
if ((flags & (ACC_STATIC | ACC_FINAL | ACC_NATIVE)) != 0) {
valid = false;
} else if (majorVersion >= JAVA_1_5_VERSION) {
if ((flags & (ACC_SYNCHRONIZED | ACC_STRICT)) != 0) {
valid = false;
}
}
}
}
if (valid) {
if (isInit) {
/*
* A specific instance initialization method (3.9) may have at most one of its ACC_PRIVATE,
* ACC_PROTECTED, and ACC_PUBLIC flags set and may also have its ACC_STRICT ACC_VARARGS, and
* ACC_SYNTHETIC flags set, but may not have any of the other flags in Table 4.5 set.
*/
valid = (flags & ~(ACC_PUBLIC | ACC_PROTECTED | ACC_PRIVATE | ACC_STRICT | ACC_SYNTHETIC | ACC_VARARGS)) == 0;
}
}
}
if (!valid) {
throw classFormatError("Invalid method flags 0x" + Integer.toHexString(flags));
}
}
protected void readMagic() {
final int magic = classfileStream.readInt();
if (magic != 0xcafebabe) {
throw classFormatError("Invalid magic number 0x" + Integer.toHexString(magic));
}
}
protected InterfaceActor resolveInterface(int index) {
final ClassConstant classConstant = constantPool.classAt(index, "interface name");
try {
return (InterfaceActor) classConstant.resolve(constantPool, index);
} catch (ClassCastException classCastException) {
throw incompatibleClassChangeError(classConstant.typeDescriptor().toJavaString() + " is not an interface");
}
}
protected InterfaceActor[] readInterfaces() {
final int nInterfaces = classfileStream.readUnsigned2();
if (nInterfaces == 0) {
return ClassActor.NO_INTERFACES;
}
final InterfaceActor[] interfaceActors = new InterfaceActor[nInterfaces];
for (int i = 0; i < nInterfaces; i++) {
interfaceActors[i] = resolveInterface(classfileStream.readUnsigned2());
}
return interfaceActors;
}
protected FieldActor[] readFields(boolean isInterface) {
final int nFields = classfileStream.readUnsigned2();
if (nFields == 0) {
// save time and space for classes that have no fields
return ClassActor.NO_FIELDS;
}
final FieldActor[] fieldActors = new FieldActor[nFields];
int nextFieldIndex = 0;
final MemberSet fieldActorSet = new MemberSet(nFields);
nextField:
for (int i = 0; i < nFields; i++) {
int flags = classfileStream.readUnsigned2();
final int nameIndex = classfileStream.readUnsigned2();
final Utf8Constant name = constantPool.utf8ConstantAt(nameIndex, "field name");
verifyFieldName(name);
final boolean isStatic = isStatic(flags);
try {
enterContext(new Object() {
@Override
public String toString() {
return "parsing field \"" + name + "\"";
}
});
final int descriptorIndex = classfileStream.readUnsigned2();
final TypeDescriptor descriptor = parseTypeDescriptor(constantPool.utf8At(descriptorIndex, "field descriptor").toString());
verifyFieldFlags(name.toString(), flags, isInterface);
char constantValueIndex = 0;
byte[] runtimeVisibleAnnotationsBytes = NO_RUNTIME_VISIBLE_ANNOTATION_BYTES;
Utf8Constant genericSignature = NO_GENERIC_SIGNATURE;
int nAttributes = classfileStream.readUnsigned2();
while (nAttributes-- != 0) {
final int attributeNameIndex = classfileStream.readUnsigned2();
final String attributeName = constantPool.utf8At(attributeNameIndex, "attribute name").toString();
final int attributeSize = classfileStream.readSize4();
final int startPosition = classfileStream.getPosition();
if (isStatic && attributeName.equals("ConstantValue")) {
if (constantValueIndex != 0) {
throw classFormatError("Duplicate ConstantValue attribute");
}
constantValueIndex = (char) classfileStream.readUnsigned2();
if (constantValueIndex == 0) {
throw classFormatError("Invalid ConstantValue index");
}
} else if (attributeName.equals("Deprecated")) {
flags |= Actor.DEPRECATED;
} else if (attributeName.equals("Synthetic")) {
flags |= Actor.ACC_SYNTHETIC;
} else if (attributeName.equals(MaxineFlags.NAME)) {
flags |= classfileStream.readSigned4();
} else if (majorVersion >= JAVA_1_5_VERSION) {
if (attributeName.equals("Signature")) {
genericSignature = constantPool.utf8At(classfileStream.readUnsigned2(), "signature index");
} else if (attributeName.equals("RuntimeVisibleAnnotations")) {
runtimeVisibleAnnotationsBytes = classfileStream.readByteArray(attributeSize);
} else {
classfileStream.skip(attributeSize);
}
} else {
classfileStream.skip(attributeSize);
}
if (attributeSize != classfileStream.getPosition() - startPosition) {
throw classFormatError("Invalid attribute_length for " + attributeName + " attribute");
}
}
if (MaxineVM.isHosted() && runtimeVisibleAnnotationsBytes != null) {
for (Annotation annotation : getAnnotations(name, descriptor)) {
if (annotation.annotationType() == HOSTED_ONLY.class) {
continue nextField;
} else if (annotation.annotationType() == PLATFORM.class) {
if (!Platform.platform().isAcceptedBy((PLATFORM) annotation)) {
continue nextField;
}
} else if (annotation.annotationType() == JDK_VERSION.class) {
if (!JDK.thisVersionOrNewer((JDK_VERSION) annotation)) {
continue nextField;
}
} else if (annotation.annotationType() == RESET.class) {
assert !Actor.isFinal(flags) :
"A final field cannot have the RESET annotation: " + classDescriptor.toJavaString() + "." + name;
} else if (annotation.annotationType() == CONSTANT.class) {
flags |= CONSTANT;
} else if (annotation.annotationType() == CONSTANT_WHEN_NOT_ZERO.class) {
flags |= CONSTANT_WHEN_NOT_ZERO;
}
}
}
final Kind kind = descriptor.toKind();
if (kind == Kind.VOID) {
throw classFormatError("Fields cannot be of type void");
}
final FieldActor fieldActor = new FieldActor(kind, name, descriptor, flags);
if (constantValueIndex != 0) {
switch (fieldActor.kind.asEnum) {
case BYTE: {
classRegistry.set(CONSTANT_VALUE, fieldActor, ByteValue.from((byte) constantPool.intAt(constantValueIndex)));
break;
}
case BOOLEAN: {
classRegistry.set(CONSTANT_VALUE, fieldActor, BooleanValue.from(constantPool.intAt(constantValueIndex) != 0));
break;
}
case CHAR: {
classRegistry.set(CONSTANT_VALUE, fieldActor, CharValue.from((char) constantPool.intAt(constantValueIndex)));
break;
}
case SHORT: {
classRegistry.set(CONSTANT_VALUE, fieldActor, ShortValue.from((short) constantPool.intAt(constantValueIndex)));
break;
}
case INT: {
classRegistry.set(CONSTANT_VALUE, fieldActor, IntValue.from(constantPool.intAt(constantValueIndex)));
break;
}
case FLOAT: {
classRegistry.set(CONSTANT_VALUE, fieldActor, FloatValue.from(constantPool.floatAt(constantValueIndex)));
break;
}
case LONG: {
classRegistry.set(CONSTANT_VALUE, fieldActor, LongValue.from(constantPool.longAt(constantValueIndex)));
break;
}
case DOUBLE: {
classRegistry.set(CONSTANT_VALUE, fieldActor, DoubleValue.from(constantPool.doubleAt(constantValueIndex)));
break;
}
case REFERENCE: {
if (!descriptor.equals(STRING)) {
throw classFormatError("Invalid ConstantValue attribute");
}
classRegistry.set(CONSTANT_VALUE, fieldActor, ReferenceValue.from(constantPool.stringAt(constantValueIndex)));
break;
}
default: {
throw classFormatError("Cannot have ConstantValue for fields of type " + kind);
}
}
}
classRegistry.set(GENERIC_SIGNATURE, fieldActor, genericSignature);
classRegistry.set(RUNTIME_VISIBLE_ANNOTATION_BYTES, fieldActor, runtimeVisibleAnnotationsBytes);
// Check for duplicates
if (fieldActorSet.add(fieldActor) != null) {
throw classFormatError("Duplicate field name and signature: " + name + " " + descriptor);
}
fieldActors[nextFieldIndex++] = fieldActor;
} finally {
exitContext();
}
}
if (nextFieldIndex < nFields) {
return Arrays.copyOf(fieldActors, nextFieldIndex);
}
return fieldActors;
}
protected TypeDescriptor[] readCheckedExceptionsAttribute() {
final int nExceptionClasses = classfileStream.readUnsigned2();
final TypeDescriptor[] checkedExceptions = new TypeDescriptor[nExceptionClasses];
for (int i = 0; i < nExceptionClasses; i++) {
final int checkedExceptionIndex = classfileStream.readUnsigned2();
checkedExceptions[i] = constantPool.classAt(checkedExceptionIndex).typeDescriptor();
}
return checkedExceptions;
}
protected ExceptionHandlerEntry readExceptionHandlerEntry(int codeLength) {
final int startBCI = classfileStream.readUnsigned2();
final int endBCI = classfileStream.readUnsigned2();
final int catchBCI = classfileStream.readUnsigned2();
final int catchTypeCPI = classfileStream.readUnsigned2();
if (startBCI >= codeLength || endBCI > codeLength || startBCI >= endBCI || catchBCI >= codeLength) {
throw classFormatError("Invalid exception handler code range");
}
// Check the index and type of the catch type
if (catchTypeCPI != 0) {
constantPool.classAt(catchTypeCPI, "catch type in exception table");
}
return new ExceptionHandlerEntry(startBCI, endBCI, catchBCI, catchTypeCPI);
}
protected ExceptionHandlerEntry[] readExceptionHandlerTable(int codeLength) {
final int nEntries = classfileStream.readUnsigned2();
if (nEntries != 0) {
final ExceptionHandlerEntry[] entries = new ExceptionHandlerEntry[nEntries];
for (int i = 0; i < nEntries; i++) {
entries[i] = readExceptionHandlerEntry(codeLength);
}
return entries;
}
return ExceptionHandlerEntry.NONE;
}
// CheckStyle: stop parameter assignment check
protected Map<LocalVariableTable.Entry, LocalVariableTable.Entry> readLocalVariableTable(int maxLocals, int codeLength, Map<LocalVariableTable.Entry, LocalVariableTable.Entry> localVariableTableEntries, boolean forLVTT) {
final int count = classfileStream.readUnsigned2();
if (count == 0) {
return localVariableTableEntries;
}
if (localVariableTableEntries == null) {
localVariableTableEntries = new HashMap<LocalVariableTable.Entry, LocalVariableTable.Entry>(count);
}
for (int i = 0; i != count; ++i) {
final LocalVariableTable.Entry entry = new LocalVariableTable.Entry(classfileStream, forLVTT);
entry.verify(constantPool, codeLength, maxLocals, forLVTT);
if (localVariableTableEntries.put(entry, entry) != null) {
throw classFormatError("Duplicated " + (forLVTT ? "LocalVariableTypeTable" : "LocalVariableTable") + " entry at index " + i);
}
}
return localVariableTableEntries;
}
// CheckStyle: resume parameter assignment check
protected CodeAttribute readCodeAttribute(int methodAccessFlags) {
final char maxStack = (char) classfileStream.readUnsigned2();
final char maxLocals = (char) classfileStream.readUnsigned2();
final int codeLength = classfileStream.readSize4();
if (codeLength <= 0) {
throw classFormatError("The value of code_length must be greater than 0");
} else if (codeLength >= 0xFFFF) {
throw classFormatError("Method code longer than 64 KB");
}
final byte[] code = classfileStream.readByteArray(codeLength);
final ExceptionHandlerEntry[] exceptionHandlerTable = readExceptionHandlerTable(code.length);
LineNumberTable lineNumberTable = LineNumberTable.EMPTY;
LocalVariableTable localVariableTable = LocalVariableTable.EMPTY;
Map<LocalVariableTable.Entry, LocalVariableTable.Entry> localVariableTableEntries = null;
Map<LocalVariableTable.Entry, LocalVariableTable.Entry> localVariableTypeTableEntries = null;
StackMapTable stackMapTable = null;
int nAttributes = classfileStream.readUnsigned2();
while (nAttributes-- != 0) {
final int attributeNameIndex = classfileStream.readUnsigned2();
final String attributeName = constantPool.utf8At(attributeNameIndex, "attribute name").toString();
final int attributeSize = classfileStream.readSize4();
final int startPosition = classfileStream.getPosition();
if (attributeName.equals("LineNumberTable")) {
lineNumberTable = new LineNumberTable(lineNumberTable, classfileStream, codeLength);
} else if (attributeName.equals("StackMapTable")) {
if (stackMapTable != null) {
throw classFormatError("Duplicate stack map attribute");
}
stackMapTable = new StackMapTable(classfileStream, constantPool, attributeSize);
} else if (attributeName.equals("LocalVariableTable")) {
localVariableTableEntries = readLocalVariableTable(maxLocals, codeLength, localVariableTableEntries, false);
} else if (majorVersion >= JAVA_1_5_VERSION) {
if (attributeName.equals("LocalVariableTypeTable")) {
localVariableTypeTableEntries = readLocalVariableTable(maxLocals, codeLength, localVariableTypeTableEntries, true);
} else {
classfileStream.skip(attributeSize);
}
} else {
classfileStream.skip(attributeSize);
}
if (attributeSize != classfileStream.getPosition() - startPosition) {
throw classFormatError("Invalid attribute length for " + attributeName + " attribute");
}
}
if (localVariableTypeTableEntries != null) {
if (localVariableTableEntries == null) {
throw classFormatError("LocalVariableTypeTable attribute present without LocalVariableTable");
}
for (LocalVariableTable.Entry lvttEntry : localVariableTypeTableEntries.values()) {
final LocalVariableTable.Entry lvtEntry = localVariableTableEntries.get(lvttEntry);
if (lvtEntry == null) {
throw classFormatError("LocalVariableTypeTable entry does not match any LocalVariableTable entry");
}
lvtEntry.copySignatureIndex(lvttEntry);
}
}
if (localVariableTableEntries != null) {
localVariableTable = new LocalVariableTable(localVariableTableEntries.values());
}
return new CodeAttribute(
constantPool,
code,
maxStack,
maxLocals,
exceptionHandlerTable,
lineNumberTable,
localVariableTable,
stackMapTable);
}
/**
* Checks that a given method signature does not contain a {@linkplain Kind#REFERENCE reference} type.
*
* @param annotationClass the annotation applied to the method that enforces this constraint
*/
protected void ensureSignatureIsPrimitive(SignatureDescriptor descriptor, Class annotationClass) {
// Verify that there are no REFERENCE parameters
for (int i = 0; i < descriptor.numberOfParameters(); ++i) {
final Kind kind = descriptor.parameterDescriptorAt(i).toKind();
ProgramError.check(kind != Kind.REFERENCE, annotationClass.getSimpleName() + " annotated methods cannot have reference parameters: " + descriptor);
}
ProgramError.check(descriptor.resultKind() != Kind.REFERENCE, annotationClass.getSimpleName() + " annotated methods cannot have reference return type: " + descriptor);
}
/**
* Gets the declaration-like string for a field or method.
*
* @param name the name of the field or method
* @param descriptor the type/signature of the field/method
*/
private String memberString(Utf8Constant name, Descriptor descriptor) {
if (descriptor instanceof TypeDescriptor) {
// A field
return classDescriptor.toJavaString() + "." + name + ' ' + ((TypeDescriptor) descriptor).toJavaString();
}
// A method
return classDescriptor.toJavaString() + "." + name + ((SignatureDescriptor) descriptor).toJavaString(false, true);
}
@HOSTED_ONLY
private Annotation[] getAnnotations(Utf8Constant name, Descriptor descriptor) {
if (name == null) {
try {
Class holder = Classes.forName(classDescriptor.toJavaString(), false, ClassfileReader.class.getClassLoader());
return holder.getAnnotations();
} catch (NoClassDefFoundError e) {
// This occurs for synthesized classes that are not available on the class path
return new Annotation[0];
}
}
Class holder = Classes.forName(classDescriptor.toJavaString(), false, ClassfileReader.class.getClassLoader());
Annotation[] annotations;
if (name.equals(SymbolTable.INIT)) {
SignatureDescriptor sig = (SignatureDescriptor) descriptor;
annotations = Classes.getDeclaredConstructor(holder, sig.resolveParameterTypes(ClassfileReader.class.getClassLoader())).getAnnotations();
} else if (descriptor instanceof TypeDescriptor) {
annotations = Classes.getDeclaredField(holder, name.string).getAnnotations();
} else {
SignatureDescriptor sig = (SignatureDescriptor) descriptor;
annotations = Classes.getDeclaredMethod(holder, name.string, sig.resolveParameterTypes(ClassfileReader.class.getClassLoader())).getAnnotations();
}
return annotations;
}
@HOSTED_ONLY
public static final HashSet<Class<? extends Annotation>> bytecodeTemplateClasses = new HashSet<Class<? extends Annotation>>();
@HOSTED_ONLY
private static boolean isBytecodeTemplate(Class<? extends Annotation> anno) {
return bytecodeTemplateClasses.contains(anno);
}
protected MethodActor[] readMethods(boolean isInterface) {
final int numberOfMethods = classfileStream.readUnsigned2();
if (numberOfMethods == 0) {
return ClassActor.NO_METHODS;
}
final MethodActor[] methodActors = new MethodActor[numberOfMethods];
int nextMethodIndex = 0;
// A map for efficiently checking the uniqueness of methods
final MemberSet methodActorSet = new MemberSet(numberOfMethods);
int clinitIndex = -1;
nextMethod:
for (int i = 0; i < numberOfMethods; ++i) {
int flags = classfileStream.readUnsigned2();
final int nameIndex = classfileStream.readUnsigned2();
Utf8Constant name = constantPool.utf8ConstantAt(nameIndex, "method name");
verifyMethodName(name, true);
int extraFlags = flags;
boolean isClinit = false;
boolean isInit = false;
if (name.equals(SymbolTable.CLINIT)) {
// Class and interface initialization methods (3.9) are called
// implicitly by the Java virtual machine; the value of their
// access_flags item is ignored except for the settings of the
// ACC_STRICT flag.
flags &= ACC_STRICT;
flags |= ACC_STATIC;
extraFlags = INITIALIZER | flags;
isClinit = true;
} else if (name.equals(SymbolTable.INIT)) {
extraFlags |= INITIALIZER;
isInit = true;
}
final boolean isStatic = isStatic(extraFlags);
try {
enterContext(new Object() {
@Override
public String toString() {
return "parsing method \"" + constantPool.utf8ConstantAt(nameIndex, "method name") + "\"";
}
});
verifyMethodFlags(flags, isInterface, isInit, isClinit, majorVersion);
flags = extraFlags;
final int descriptorIndex = classfileStream.readUnsigned2();
final SignatureDescriptor descriptor = SignatureDescriptor.create(constantPool.utf8At(descriptorIndex, "method descriptor"));
if (descriptor.computeNumberOfSlots() + (isStatic ? 0 : 1) > 255) {
throw classFormatError("Too many arguments in method signature: " + descriptor);
}
if (name.equals(SymbolTable.FINALIZE) && descriptor.equals(SignatureDescriptor.VOID) && (flags & ACC_STATIC) == 0) {
// this class has a finalizer method implementation
// (this bit will be cleared for java.lang.Object later)
classFlags |= FINALIZER;
}
CodeAttribute codeAttribute = null;
TypeDescriptor[] checkedExceptions = NO_CHECKED_EXCEPTIONS;
byte[] runtimeVisibleAnnotationsBytes = NO_RUNTIME_VISIBLE_ANNOTATION_BYTES;
byte[] runtimeVisibleParameterAnnotationsBytes = NO_RUNTIME_VISIBLE_PARAMETER_ANNOTATION_BYTES;
byte[] annotationDefaultBytes = NO_ANNOTATION_DEFAULT_BYTES;
Utf8Constant genericSignature = NO_GENERIC_SIGNATURE;
int nAttributes = classfileStream.readUnsigned2();
while (nAttributes-- != 0) {
final int attributeNameIndex = classfileStream.readUnsigned2();
final String attributeName = constantPool.utf8At(attributeNameIndex, "attribute name").toString();
final int attributeSize = classfileStream.readSize4();
final int startPosition = classfileStream.getPosition();
if (attributeName.equals("Code")) {
if (codeAttribute != null) {
throw classFormatError("Duplicate Code attribute");
}
codeAttribute = readCodeAttribute(flags);
} else if (attributeName.equals("Exceptions")) {
if (checkedExceptions != NO_CHECKED_EXCEPTIONS) {
throw classFormatError("Duplicate Exceptions attribute");
}
checkedExceptions = readCheckedExceptionsAttribute();
} else if (attributeName.equals("Deprecated")) {
flags |= Actor.DEPRECATED;
} else if (attributeName.equals("Synthetic")) {
flags |= Actor.ACC_SYNTHETIC;
} else if (attributeName.equals(MaxineFlags.NAME)) {
flags |= classfileStream.readSigned4();
} else if (majorVersion >= JAVA_1_5_VERSION) {
if (attributeName.equals("Signature")) {
genericSignature = constantPool.utf8At(classfileStream.readUnsigned2(), "signature index");
} else if (attributeName.equals("RuntimeVisibleAnnotations")) {
runtimeVisibleAnnotationsBytes = classfileStream.readByteArray(attributeSize);
} else if (attributeName.equals("RuntimeVisibleParameterAnnotations")) {
runtimeVisibleParameterAnnotationsBytes = classfileStream.readByteArray(attributeSize);
} else if (attributeName.equals("AnnotationDefault")) {
annotationDefaultBytes = classfileStream.readByteArray(attributeSize);
} else {
classfileStream.skip(attributeSize);
}
} else {
classfileStream.skip(attributeSize);
}
final int distance = classfileStream.getPosition() - startPosition;
if (attributeSize != distance) {
final String message = "Invalid attribute_length for " + attributeName + " attribute (reported " + attributeSize + " != parsed " + distance + ")";
throw classFormatError(message);
}
}
if (isAbstract(flags) || isNative(flags)) {
if (codeAttribute != null) {
throw classFormatError("Code attribute supplied for native or abstract method");
}
} else {
if (codeAttribute == null) {
throw classFormatError("Missing Code attribute");
}
}
if (MaxineVM.isHosted()) {
// This must be called before clinit methods are filtered, since we especially want to find
// usages of the Unsafe class in static initializers that are not re-executed at run time.
UnsafeUsageChecker.methodLoadedHook(classDescriptor, name, codeAttribute);
if (isClinit) {
// Class initializer's for all boot image classes are run while bootstrapping and do not need to be in the boot image.
// The "max.loader.preserveClinitMethods" system property can be used to override this default behaviour.
// and specific (JDK) classes can be designated as overrides for reinitialisation purposes.
if (!MaxineVM.keepClassInit(classDescriptor)) {
continue nextMethod;
}
}
if (isStatic) {
// The following helps folding down enum switch code for known enums, which should not incur binary compatibility issues:
if (classDescriptor.string.startsWith("Lcom/sun/max") && name.toString().startsWith("$SWITCH_TABLE")) {
// TODO: check for the switch table name generated by javac
flags |= FOLD;
}
}
}
int substituteeIndex = -1;
String intrinsic = null;
Class accessor = null;
boolean classHasNeverInlineAnnotation = false;
if (MaxineVM.isHosted()) {
for (Annotation annotation : getAnnotations(null, null)) {
if (annotation.annotationType() == NEVER_INLINE.class) {
classHasNeverInlineAnnotation = true;
break;
}
}
}
if (MaxineVM.isHosted() && runtimeVisibleAnnotationsBytes != null) {
for (Annotation annotation : getAnnotations(name, descriptor)) {
if (annotation.annotationType() == HOSTED_ONLY.class) {
continue nextMethod;
} else if (annotation.annotationType() == C_FUNCTION.class) {
ensureSignatureIsPrimitive(descriptor, C_FUNCTION.class);
ProgramError.check(isStatic(flags), "Cannot apply " + C_FUNCTION.class.getName() + " to a non-static method: " + memberString(name, descriptor));
ProgramError.check(isNative(flags), "Cannot apply " + C_FUNCTION.class.getName() + " to a non-native method: " + memberString(name, descriptor));
flags |= C_FUNCTION;
} else if (annotation.annotationType() == VM_ENTRY_POINT.class) {
ensureSignatureIsPrimitive(descriptor, VM_ENTRY_POINT.class);
ProgramError.check(isStatic(flags), "Cannot apply " + VM_ENTRY_POINT.class.getName() + " to a non-static method: " + memberString(name, descriptor));
flags |= VM_ENTRY_POINT;
} else if (annotation.annotationType() == NO_SAFEPOINT_POLLS.class) {
flags |= NO_SAFEPOINT_POLLS;
} else if (annotation.annotationType() == ACCESSOR.class) {
accessor = ((ACCESSOR) annotation).value();
flags |= UNSAFE;
} else if (annotation.annotationType() == PLATFORM.class) {
if (!Platform.platform().isAcceptedBy((PLATFORM) annotation)) {
continue nextMethod;
}
} else if (annotation.annotationType() == JDK_VERSION.class) {
if (!JDK.thisVersionOrNewer((JDK_VERSION) annotation)) {
continue nextMethod;
}
} else if (isBytecodeTemplate(annotation.annotationType())) {
flags |= TEMPLATE | UNSAFE | INLINE;
} else if (annotation.annotationType() == INLINE.class) {
flags |= INLINE;
ProgramError.check(!isAbstract(flags), "Cannot apply " + INLINE.class.getName() + " to an abstract method: " + memberString(name, descriptor));
} else if (annotation.annotationType() == NEVER_INLINE.class) {
flags |= NEVER_INLINE;
} else if (annotation.annotationType() == INTRINSIC.class) {
INTRINSIC intrinsicAnnotation = (INTRINSIC) annotation;
intrinsic = intrinsicAnnotation.value();
if (intrinsic == MaxineIntrinsicIDs.UNSAFE_CAST) {
String anno = INTRINSIC.class.getSimpleName() + "(UNSAFE_CAST)";
ProgramError.check(genericSignature == null, "Cannot apply " + anno + " to a generic method: " + memberString(name, descriptor));
ProgramError.check(descriptor.resultKind() != Kind.VOID, "Cannot apply " + anno + " to a void method: " + memberString(name, descriptor));
ProgramError.check(descriptor.numberOfParameters() == (isStatic ? 1 : 0), "Can only apply " + anno +
" to a method with exactly one parameter: " + memberString(name, descriptor));
}
} else if (annotation.annotationType() == FOLD.class) {
flags |= FOLD;
} else if (annotation.annotationType() == LOCAL_SUBSTITUTION.class) {
// process any class-local substitutions
flags |= LOCAL_SUBSTITUTE;
final Utf8Constant substituteeName = SymbolTable.lookupSymbol(toSubstituteeName(name.toString()));
if (substituteeName != null) {
for (int j = nextMethodIndex - 1; j >= 0; --j) {
final MethodActor substituteeActor = methodActors[j];
if (substituteeActor.name.equals(substituteeName) && substituteeActor.descriptor().equals(descriptor)) {
ProgramError.check(isStatic == substituteeActor.isStatic());
Trace.line(2, "Substituted " + classDescriptor.toJavaString() + "." + substituteeName + descriptor);
Trace.line(2, " with " + classDescriptor.toJavaString() + "." + name + descriptor);
substituteeIndex = j;
name = substituteeName;
// Copy the access level of the substitutee to the local substitute
final int accessFlagsMask = ACC_PUBLIC | ACC_PRIVATE | ACC_PROTECTED;
flags &= ~accessFlagsMask;
flags |= substituteeActor.flags() & accessFlagsMask;
flags |= substituteeActor.flags() & SUBSTITUTION_ADOPTED_FLAGS;
break;
}
}
}
ProgramError.check(substituteeIndex != -1, "Could not find substitutee for local substitute method: " + memberString(name, descriptor));
}
if (intrinsic != null) {
// discard bytecode for intrinsic methods
codeAttribute = null;
}
}
}
if (classHasNeverInlineAnnotation && !isInline(flags)) {
flags |= NEVER_INLINE;
}
if (isNative(flags)) {
flags |= UNSAFE;
}
if (isNative(flags)) {
flags |= UNSAFE;
}
if (MaxineVM.isHosted() && classDescriptor.toKind() == Kind.WORD) {
// All non-static methods in Word types must be either @INLINE, @FOLD, @INTRINSIC, or @HOSTED_ONLY.
// They have special semantics, such as that no dynamic dispatch is possible, that no null checks must
// be performed when calling them, and that the receiver is not an Object.
// It simplifies the compiler if they never end up as method calls, but are always optimized away by the compiler.
if (Actor.isSynthetic(flags) && !Actor.isInitializer(flags) && !Actor.isDeclaredFoldable(flags) && !Actor.isInline(flags)) {
// When methods are overridden with a changed return type, javac automatically inserts a synthetic method with the
// return type of the super class method. Since we cannot annotate such methods in source code, we add the inline flag here.
flags |= INLINE;
}
if (!Actor.isStatic(flags) && !Actor.isInline(flags) && !Actor.isDeclaredFoldable(flags) && intrinsic == null) {
throw FatalError.unexpected("Non-static methods in Word types must be either @INLINE, @FOLD, @INTRINSIC, or @HOSTED_ONLY: " + classDescriptor.toJavaString() + "." + name + " " + descriptor.toJavaString(true, true));
}
}
final MethodActor methodActor;
if (isInterface) {
if (isClinit) {
methodActor = new StaticMethodActor(name, descriptor, flags, codeAttribute, intrinsic);
} else if (isInit) {
throw classFormatError("Interface cannot have a constructor");
} else {
methodActor = new InterfaceMethodActor(name, descriptor, flags, intrinsic);
}
} else if (isStatic) {
methodActor = new StaticMethodActor(name, descriptor, flags, codeAttribute, intrinsic);
} else {
methodActor = new VirtualMethodActor(name, descriptor, flags, codeAttribute, intrinsic);
}
classRegistry.set(GENERIC_SIGNATURE, methodActor, genericSignature);
classRegistry.set(CHECKED_EXCEPTIONS, methodActor, checkedExceptions);
classRegistry.set(Property.ACCESSOR, methodActor, accessor);
classRegistry.set(RUNTIME_VISIBLE_ANNOTATION_BYTES, methodActor, runtimeVisibleAnnotationsBytes);
classRegistry.set(RUNTIME_VISIBLE_PARAMETER_ANNOTATION_BYTES, methodActor, runtimeVisibleParameterAnnotationsBytes);
classRegistry.set(ANNOTATION_DEFAULT_BYTES, methodActor, annotationDefaultBytes);
if (MaxineVM.isHosted() && substituteeIndex != -1) {
methodActors[substituteeIndex] = methodActor;
} else {
if (methodActorSet.add(methodActor) != null) {
throw classFormatError("Duplicate method name and signature: " + name + " " + descriptor);
}
if (isClinit) {
clinitIndex = nextMethodIndex;
}
methodActors[nextMethodIndex++] = methodActor;
}
} finally {
exitContext();
}
}
MethodActor[] result = nextMethodIndex < numberOfMethods ? Arrays.copyOf(methodActors, nextMethodIndex) : methodActors;
if (clinitIndex != -1) {
if (clinitIndex < result.length - 1) {
// Move <clinit> to the end of the array. This makes member indexes
// consistent, no matter whether the execution context omits clinit methods.
MethodActor clinit = result[clinitIndex];
for (int i = clinitIndex; i < result.length - 1; i++) {
result[i] = result[i + 1];
}
result[result.length - 1] = clinit;
}
}
return result;
}
protected void readInnerClassesAttribute() {
if (classInnerClasses != null) {
throw classFormatError("Duplicate InnerClass attribute");
}
final int nInnerClasses = classfileStream.readUnsigned2();
final InnerClassInfo[] innerClassInfos = new InnerClassInfo[nInnerClasses];
final TypeDescriptor[] innerClasses = new TypeDescriptor[innerClassInfos.length];
int nextInnerClass = 0;
for (int i = 0; i < nInnerClasses; ++i) {
final InnerClassInfo innerClassInfo = new InnerClassInfo(classfileStream, constantPool);
final int innerClassIndex = innerClassInfo.innerClassIndex();
final int outerClassIndex = innerClassInfo.outerClassIndex();
// The JVM specification allows a null inner class but don't ask me what it means!
if (innerClassIndex == 0) {
continue;
}
// If no outer class is specified, then this entry denotes a local or anonymous class
// that will have an EnclosingMethod attribute instead. That is, these classes are
// not *immediately* enclosed by another class
if (outerClassIndex == 0) {
continue;
}
// If this entry refers to the current class, then the current class must be an inner class:
// it's enclosing class is recorded and it's flags are updated.
final TypeDescriptor innerClassDescriptor = constantPool.classAt(innerClassIndex, "inner class descriptor").typeDescriptor();
if (innerClassDescriptor.equals(classDescriptor)) {
if (classOuterClass != null) {
throw classFormatError("duplicate outer class");
}
classFlags |= INNER_CLASS;
classOuterClass = constantPool.classAt(outerClassIndex).typeDescriptor();
classFlags |= innerClassInfo.flags();
}
final TypeDescriptor outerClassDescriptor = constantPool.classAt(outerClassIndex, "outer class descriptor").typeDescriptor();
if (outerClassDescriptor.equals(classDescriptor)) {
// The inner class is enclosed by the current class
innerClasses[nextInnerClass++] = constantPool.classAt(innerClassIndex).typeDescriptor();
} else {
// The inner class is enclosed by some class other than current class: ignore it
}
for (int j = 0; j < i; ++j) {
final InnerClassInfo otherInnerClassInfo = innerClassInfos[j];
if (otherInnerClassInfo != null) {
if (innerClassIndex == otherInnerClassInfo.innerClassIndex() && outerClassIndex == otherInnerClassInfo.outerClassIndex()) {
throw classFormatError("Duplicate entry in InnerClasses attribute");
}
}
}
innerClassInfos[i] = innerClassInfo;
}
if (nextInnerClass != 0) {
if (nextInnerClass == innerClasses.length) {
classInnerClasses = innerClasses;
} else {
classInnerClasses = new TypeDescriptor[nextInnerClass];
System.arraycopy(innerClasses, 0, classInnerClasses, 0, nextInnerClass);
}
}
}
protected EnclosingMethodInfo readEnclosingMethodAttribute() {
final int classIndex = classfileStream.readUnsigned2();
final int nameAndTypeIndex = classfileStream.readUnsigned2();
final ClassConstant holder = constantPool.classAt(classIndex);
final String name;
final String descriptor;
if (nameAndTypeIndex != 0) {
final NameAndTypeConstant nameAndType = constantPool.nameAndTypeAt(nameAndTypeIndex);
name = nameAndType.name().toString();
descriptor = nameAndType.descriptorString();
} else {
name = null;
descriptor = null;
}
return new EnclosingMethodInfo(holder.typeDescriptor(), name, descriptor);
}
protected ClassActor resolveSuperClass(int superClassIndex, boolean isInterface) {
final ClassActor superClassActor;
if (superClassIndex != 0) {
final TypeDescriptor superClassDescriptor = constantPool.classAt(superClassIndex, "super class descriptor").typeDescriptor();
if (superClassDescriptor.equals(classDescriptor)) {
throw classFormatError("Class cannot be its own super class");
}
/*
* If this is an interface class, its superclass must be
* java.lang.Object.
*/
if (isInterface && !superClassDescriptor.equals(OBJECT)) {
throw classFormatError("Interface class must inherit from java.lang.Object");
}
/*
* Now ensure the super class is resolved
*/
superClassActor = constantPool.classAt(superClassIndex).resolve(constantPool, superClassIndex);
/*
* Cannot inherit from an array class.
*/
if (superClassActor.isArrayClass()) {
throw classFormatError("Cannot inherit from array class");
}
/*
* The superclass cannot be an interface. From the
* JVM Spec section 5.3.5:
*
* If the class of interface named as the direct
* superclass of C is in fact an interface, loading
* throws an IncompatibleClassChangeError.
*/
if (superClassActor.isInterface()) {
throw classFormatError("Cannot extend an interface class");
}
/*
* The superclass cannot be final.
*/
if (superClassActor.isFinal()) {
throw verifyError("Cannot extend a final class " + superClassActor.name);
}
} else {
if (!classDescriptor.equals(OBJECT)) {
throw classFormatError("missing required super class");
}
superClassActor = null;
}
return superClassActor;
}
/**
* Loads a class from the configured {@linkplain #classfileStream class file stream}.
*
* @param nameExpected the expected name of the class in the stream (can be {@code null})
* @param isRemote specifies if the stream is from a remote/untrusted (e.g. network) source. This is mainly used to
* determine the default bytecode verification policy for the class.
*/
private ClassActor loadClass0(String nameExpected, boolean isRemote) {
readMagic();
final char minorVersionChar = (char) classfileStream.readUnsigned2();
final char majorVersionChar = (char) classfileStream.readUnsigned2();
verifyVersion(majorVersionChar, minorVersionChar);
constantPool = new ConstantPool(classLoader, classfileStream);
majorVersion = majorVersionChar;
classFlags = classfileStream.readUnsigned2();
verifyClassFlags(classFlags, majorVersionChar);
final boolean isInterface = isInterface(classFlags);
final int thisClassIndex = classfileStream.readUnsigned2();
classDescriptor = constantPool.classAt(thisClassIndex, "this class descriptor").typeDescriptor();
String nameLoaded = classDescriptor.toJavaString();
if (nameExpected != null && !nameExpected.equals(nameLoaded)) {
/*
* VMSpec 5.3.5:
*
* Otherwise, if the purported representation does not actually
* represent a class named N, loading throws an instance of
* NoClassDefFoundError or an instance of one of its
* subclasses.
*/
throw noClassDefFoundError("'this_class' indicates wrong type");
}
Utf8Constant name = SymbolTable.makeSymbol(nameLoaded);
final int superClassIndex = classfileStream.readUnsigned2();
final ClassActor superClassActor = resolveSuperClass(superClassIndex, isInterface);
final InterfaceActor[] interfaceActors = readInterfaces();
final FieldActor[] fieldActors = readFields(isInterface);
final MethodActor[] methodActors = readMethods(isInterface);
String sourceFileName = null;
byte[] runtimeVisibleAnnotationsBytes = null;
Utf8Constant genericSignature = null;
EnclosingMethodInfo enclosingMethodInfo = null;
int nAttributes = classfileStream.readUnsigned2();
while (nAttributes-- != 0) {
final int attributeNameIndex = classfileStream.readUnsigned2();
final String attributeName = constantPool.utf8At(attributeNameIndex, "attribute name").toString();
final int attributeSize = classfileStream.readSize4();
final int startPosition = classfileStream.getPosition();
if (attributeName.equals("SourceFile")) {
if (sourceFileName != null) {
throw classFormatError("Duplicate SourceFile attribute");
}
final int sourceFileNameIndex = classfileStream.readUnsigned2();
sourceFileName = constantPool.utf8At(sourceFileNameIndex, "source file name").toString();
} else if (attributeName.equals("Deprecated")) {
classFlags |= Actor.DEPRECATED;
} else if (attributeName.equals("Synthetic")) {
classFlags |= Actor.ACC_SYNTHETIC;
} else if (attributeName.equals("InnerClasses")) {
readInnerClassesAttribute();
} else if (attributeName.equals(MaxineFlags.NAME)) {
classFlags |= classfileStream.readSigned4();
} else if (majorVersion >= JAVA_1_5_VERSION) {
if (attributeName.equals("Signature")) {
genericSignature = constantPool.utf8At(classfileStream.readUnsigned2(), "signature index");
} else if (attributeName.equals("RuntimeVisibleAnnotations")) {
runtimeVisibleAnnotationsBytes = classfileStream.readByteArray(attributeSize);
} else if (attributeName.equals("EnclosingMethod")) {
if (enclosingMethodInfo != null) {
throw classFormatError("Duplicate EnclosingMethod attribute");
}
enclosingMethodInfo = readEnclosingMethodAttribute();
} else {
classfileStream.skip(attributeSize);
}
} else {
classfileStream.skip(attributeSize);
}
if (attributeSize != classfileStream.getPosition() - startPosition) {
throw classFormatError("Invalid attribute length for " + name + " attribute");
}
}
// inherit the FINALIZER flag from the super class actor
if (superClassActor != null) {
if (superClassActor.hasFinalizer()) {
classFlags |= Actor.FINALIZER;
}
} else {
// clear the finalizer bit for the java.lang.Object class; otherwise all classes would have it!
classFlags &= ~Actor.FINALIZER;
}
if (isRemote) {
classFlags |= Actor.REMOTE;
}
// Ensure there are no trailing bytes
classfileStream.checkEndOfFile();
if (MaxineVM.isHosted() && runtimeVisibleAnnotationsBytes != null) {
for (Annotation annotation : getAnnotations(null, null)) {
if (annotation.annotationType() == HOSTED_ONLY.class) {
throw new HostOnlyClassError(name.string);
}
}
}
final ClassActor classActor;
if (isInterface) {
classActor = createInterfaceActor(
constantPool,
classLoader,
name,
majorVersionChar,
minorVersionChar,
classFlags,
interfaceActors,
fieldActors,
methodActors,
genericSignature,
runtimeVisibleAnnotationsBytes,
sourceFileName,
classInnerClasses,
classOuterClass,
enclosingMethodInfo);
} else {
classActor = createTupleOrHybridClassActor(
constantPool,
classLoader,
name,
majorVersionChar,
minorVersionChar,
classFlags,
superClassActor,
interfaceActors,
fieldActors,
methodActors,
genericSignature,
runtimeVisibleAnnotationsBytes,
sourceFileName,
classInnerClasses,
classOuterClass,
enclosingMethodInfo);
}
if (superClassActor != null) {
superClassActor.checkAccessBy(classActor);
}
return classActor;
}
public static VMStringOption saveClassDir = VMOptions.register(new VMStringOption("-XX:SaveClassDir=", false, null,
"Directory to which the classfiles of loaded classes should be written."), MaxineVM.Phase.STARTING);
/**
* Loads a class from the configured {@linkplain #classfileStream class file stream}.
*
* @param name the expected name of the class in the stream (can be {@code null})
* @param source
* @param isRemote specifies if the stream is from a remote/untrusted (e.g. network) source. This is mainly used to
* determine the default bytecode verification policy for the class.
*/
private ClassActor loadClass(final String name, Object source, boolean isRemote) {
try {
String optSource = null;
boolean verbose = verboseOption.verboseClass || Trace.hasLevel(2);
if (verbose) {
if (source != null) {
Log.println("[Loading " + name + " from " + source + "]");
} else {
optSource = classLoader == null ? "generated data" : classLoader.getClass().getName();
Log.println("[Loading " + name + " from " + optSource + "]");
}
}
enterContext(new Object() {
@Override
public String toString() {
return "loading " + name;
}
});
final ClassActor classActor = loadClass0(name, isRemote);
if (verboseOption.verboseClass || Trace.hasLevel(2)) {
if (source != null) {
Log.println("[Loaded " + name + " from " + source + "]");
} else {
Log.println("[Loaded " + name + " from " + optSource + "]");
}
}
return classActor;
} finally {
exitContext();
}
}
/**
* Records the state of the class transformation process (when agents are active).
* Only one thread, {@code definingThread}, handles the transform, other concurrent
* requests wait for {@code definingThread} to complete.
*/
private static class ClassTransformState {
static final int INFLIGHT = 1;
static final int DEFINED = 2;
int state;
final Thread definingThread;
AgentTransformResult transformResult;
ClassTransformState() {
state = INFLIGHT;
definingThread = Thread.currentThread();
}
}
/**
* Uniquely identifies an attempted definition for a given class in a given classloader.
*/
private static class ClClass {
final ClassLoader classLoader;
final String className;
ClClass(ClassLoader classLoader, String className) {
this.classLoader = classLoader;
this.className = className;
}
@Override
public boolean equals(Object other) {
ClClass otherClClass = (ClClass) other;
return classLoader == otherClClass.classLoader && className.equals(otherClClass.className);
}
@Override
public int hashCode() {
return classLoader.hashCode() ^ className.hashCode();
}
}
/**
* Records the class transformation state (when agents are active).
*/
private static ConcurrentMap<ClClass, ClassTransformState> classTransformStateMap = new ConcurrentHashMap<ClClass, ClassTransformState>();
/**
* Captures changes to the incoming parameters to {@link ClassfileReader#defineClassActor}if a transform occurred.
*/
private static class AgentTransformResult {
byte[] bytes;
int length;
int offset;
AgentTransformResult(byte[] bytes, int length, int offset) {
this.bytes = bytes;
this.offset = offset;
this.length = length;
}
}
/**
* Check for bytecode transformation by agents prior to class definition.
* @return modified bytecode array or {@code null} if no change
*/
private static AgentTransformResult checkAgentTransform(String name, ClassLoader classLoader, byte[] bytes, ProtectionDomain protectionDomain,
int offset, int length) {
final Instrumentation instrumentation = InstrumentationManager.getInstrumentation();
boolean vmtiAgents = MaxineVM.isHosted() ? false : VMTI.handler().classFileLoadHookHandled();
/*
* When we have (Java) agents active that might transform the bytecode there are additional concurrency considerations.
* First, it isn't ok to call the agents multiple times to transform the same class bytes. Second, we have
* to worry about recursion from an agent that happens to use (and therefore tries to recursively define)
* the class we are defining here.
*
* N.B. Although the specification does not discuss this, Hotspot invokes the agent methods whenever the class
* is loaded in a distinct classloader. So we use ClClass as the key in the state map.
*
*/
if (instrumentation != null || vmtiAgents) {
ClassTransformState proto = new ClassTransformState();
ClassTransformState classTransformState = classTransformStateMap.putIfAbsent(new ClClass(classLoader, name), proto);
if (classTransformState == null) {
classTransformState = proto;
// no conflict, this thread will now handle this class
boolean changed = false;
// zero base and truncate the input array if necessary
if (offset != 0 || length != bytes.length) {
byte[] zeroBase = new byte[length];
System.arraycopy(bytes, offset, zeroBase, 0, length);
bytes = zeroBase;
}
String iName = name == null ? null : name.replace('.', '/');
ClassLoader agentClassLoader = classLoader == BootClassLoader.BOOT_CLASS_LOADER ? null : classLoader;
if (vmtiAgents) {
// Call JVMTI agents first and then pass to the javaagent agents
final byte[] tBytes = VMTI.handler().classFileLoadHook(agentClassLoader, iName, protectionDomain, bytes);
if (tBytes != null) {
changed = true;
bytes = tBytes;
}
}
if (instrumentation != null) {
final byte[] tBytes = InstrumentationManager.transform(agentClassLoader, iName, null, protectionDomain, bytes, false);
if (tBytes != null) {
changed = true;
bytes = tBytes;
}
}
synchronized (classTransformState) {
classTransformState.state = ClassTransformState.DEFINED;
if (changed) {
classTransformState.transformResult = new AgentTransformResult(bytes, 0, bytes.length);
}
// notify all the threads that are waiting for us to finish
classTransformState.notifyAll();
}
} else {
// another thread is handling the definition or we have recursion
synchronized (classTransformState) {
if (classTransformState.state == ClassTransformState.INFLIGHT) {
if (classTransformState.definingThread == Thread.currentThread()) {
// recursion from the agent itself, we just need to let this continue as a normal define
// as we have already prevented recursive transformation
return null;
} else {
// parallel define, let the other thread handle it
}
while (classTransformState.state == ClassTransformState.INFLIGHT) {
try {
classTransformState.wait();
} catch (InterruptedException ex) {
}
}
} else {
// another thread already handled it, so no transform or wait needed
}
}
}
// which ever thread handled it, this is the result
return classTransformState.transformResult;
} else {
return null; // no agents, so no change
}
}
/**
* Converts an array of bytes into a {@code ClassActor}.
*
* @param name the name of the class being defined
* @param classLoader the defining class loader
* @param bytes The bytes that make up the class data. The bytes should have the format of a valid class file as
* defined by the <a href="http://java.sun.com/docs/books/vmspec/">Java Virtual Machine Specification</a>.
* @param protectionDomain the ProtectionDomain of the class. This value can be null.
* @param source a object whose {@code toString()} method describes source location of the bytes. This is purely
* informative detail. For example, it may be used to provide the output for verbose class loading. This
* value can be null.
* @param isRemote specifies if the stream is from a remote/untrusted (e.g. network) source. This is mainly used to
* determine the default bytecode verification policy for the class.
* @return the {@code ClassActor} object created from the data, and optional {@code ProtectionDomain}
* @throws ClassFormatError if the data did not contain a valid class
* @throws NoClassDefFoundError if {@code name} is not equal to the {@linkplain Class#getName() binary name} of the
* class specified by {@code bytes}
*/
public static ClassActor defineClassActor(String name, ClassLoader classLoader, byte[] bytes, ProtectionDomain protectionDomain, Object source, boolean isRemote) {
return defineClassActor(name, classLoader, bytes, 0, bytes.length, protectionDomain, source, isRemote);
}
/**
* Converts an array of bytes into a {@code ClassActor}.
*
* @param name the name of the class being defined (can be {@code null})
* @param classLoader the defining class loader
* @param bytes the bytes that make up the class data. The bytes in positions {@code offset} through
* {@code offset + length - 1} should have the format of a valid class file as defined by the <a
* href="http://java.sun.com/docs/books/vmspec/">Java Virtual Machine Specification</a>.
* @param offset the start offset in {@code bytes} of the class data
* @param length the length of the class data
* @param protectionDomain the ProtectionDomain of the class. This value can be null.
* @param source a object whose {@code toString()} method describes source location of the bytes. This is purely
* informative detail. For example, it may be used to provide the output for verbose class loading. This
* value can be null.
* @param isRemote specifies if the stream is from a remote/untrusted (e.g. network) source. This is mainly used to
* determine the default bytecode verification policy for the class.
* @return the {@code ClassActor} object created from the data, and optional {@code ProtectionDomain}
* @throws ClassFormatError if the data did not contain a valid class
* @throws NoClassDefFoundError if {@code name} is not equal to the {@linkplain Class#getName() binary name} of the
* class specified by {@code bytes}
*/
public static ClassActor defineClassActor(String name, ClassLoader classLoader, byte[] bytes, int offset, int length, ProtectionDomain protectionDomain, Object source, boolean isRemote) {
AgentTransformResult transformResult = checkAgentTransform(name, classLoader, bytes, protectionDomain, offset, length);
if (transformResult != null) {
bytes = transformResult.bytes;
offset = transformResult.offset;
length = transformResult.length;
}
/*
* This code can execute concurrently if the class is being defined by multiple threads.
* Some redundant work is done here but ultimately only one thread wins and we get a single
* ClassActor created.
*
* It is very important to return the value generated by ClassRegistry.define, which is where
* the resolution of which thread wins the race is handled.
*/
saveClassfile(name, bytes);
final ClassfileStream classfileStream = new ClassfileStream(bytes, offset, length);
final ClassfileReader classfileReader = new ClassfileReader(classfileStream, classLoader);
ClassActor classActor = classfileReader.loadClass(name, source, isRemote);
classActor.setProtectionDomain(protectionDomain);
final ClassActor definedClassActor = ClassRegistry.define(classActor);
if (!MaxineVM.isHosted()) {
// Maxine is unable to usefully distinguish CLASS_LOAD and CLASS_PREPARE events which, for example, JVMTI distinguishes,
// as we need a ClassActor in order to create a Class object, so we just have the one event.
VMTI.handler().classLoad(definedClassActor);
}
return definedClassActor;
}
/**
* This exists (solely) for the purpose of being able to reify generated classes while hosted. These are needed so
* that the actors for generated stubs can be created.
*/
@HOSTED_ONLY
private static final Map<String, byte[]> savedClassfiles = new TreeMap<String, byte[]>();
@HOSTED_ONLY
public static ClasspathFile findGeneratedClassfile(String name) {
final byte[] classfileBytes = savedClassfiles.get(name);
if (classfileBytes != null) {
return new ClasspathFile(classfileBytes, null);
}
return null;
}
/**
* Writes all the class files that have been {@linkplain #saveClassfile(String, byte[]) saved} (either explicitly
* or as a side effect of being loaded to a given jar file).
*
* @param jarFile where the class files are to be written
*/
@HOSTED_ONLY
public static void writeClassfilesToJar(File jarFile) {
try {
final JarOutputStream jarOutputStream = new JarOutputStream(new FileOutputStream(jarFile));
jarOutputStream.setLevel(Deflater.BEST_COMPRESSION);
long timestamp = System.currentTimeMillis();
for (Map.Entry<String, byte[]> entry : savedClassfiles.entrySet()) {
String name = entry.getKey();
String classfilePath = name.replace('.', '/') + ".class";
final JarEntry jarEntry = new JarEntry(classfilePath);
jarEntry.setTime(timestamp);
try {
jarOutputStream.putNextEntry(jarEntry);
jarOutputStream.write(entry.getValue());
jarOutputStream.closeEntry();
} catch (IOException e) {
throw ProgramError.unexpected("IO error saving class file for " + name, e);
}
}
jarOutputStream.close();
} catch (IOException e) {
throw ProgramError.unexpected("IO error writing saved classes to " + jarFile, e);
}
}
/**
* Writes all the class files that have been {@linkplain #saveClassfile(String, byte[]) saved} (either explicitly
* or as a side effect of being loaded to a given directory).
*
* @param directory where the class files are to be written
*/
@HOSTED_ONLY
public static void writeClassfilesToDir(File directory) {
for (Map.Entry<String, byte[]> entry : savedClassfiles.entrySet()) {
String name = entry.getKey();
String classfilePath = name.replace('.', File.separatorChar) + ".class";
File classfile = new File(directory, classfilePath);
try {
FileOutputStream out = new FileOutputStream(classfile);
out.write(entry.getValue());
out.close();
} catch (IOException e) {
throw ProgramError.unexpected("IO error saving class file to " + classfile, e);
}
}
}
/**
* Saves a copy of a class file in the directory specified by the value of the {@link #saveClassDir} option.
* This method does nothing if the value of the {@code saveClassDir} option is {@code null}.
*
* @param name the (purported) name of the class represented in {@code classfileBytes}
* @param classfileBytes the class file bytes to save
*/
public static void saveClassfile(String name, byte[] classfileBytes) {
if (name == null) {
return;
}
String classfilePath = Classes.getPackageName(name).replace('.', File.separatorChar) + File.separatorChar + Classes.getSimpleName(name) + ".class";
if (MaxineVM.isHosted()) {
synchronized (savedClassfiles) {
byte[] existingClassfile = savedClassfiles.put(name, classfileBytes);
if (existingClassfile != null && !Arrays.equals(existingClassfile, classfileBytes)) {
try {
Class<?> javaClass = Class.forName(name);
if (javaClass.getAnnotation(HOSTED_ONLY.class) != null) {
// Don't emit messages for host only classes as these class files are only generated
// as an unavoidable side effect of bytecode intrinsification (see Intrinsics)
} else {
ProgramWarning.message("class with same name but different class file bytes generated twice: " + name);
}
} catch (ClassNotFoundException e) {
}
}
}
}
if (saveClassDir.getValue() != null) {
File classfile = new File(saveClassDir.getValue(), classfilePath);
try {
classfile.getParentFile().mkdirs();
FileOutputStream out = new FileOutputStream(classfile);
out.write(classfileBytes);
out.close();
if (verboseOption.verboseClass) {
Log.println("[Wrote class file to " + classfile + "]");
}
} catch (IOException e) {
Log.println("[Error writing class file bytes to " + classfile + ": " + e + "]");
}
}
}
}