package org.graalvm.compiler.replacements.classfile;
import java.io.DataInputStream;
import java.io.IOException;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.List;
import org.graalvm.compiler.replacements.classfile.ClassfileConstant.Utf8;
import jdk.vm.ci.meta.ResolvedJavaMethod;
import jdk.vm.ci.meta.ResolvedJavaType;
public class Classfile {
private final ResolvedJavaType type;
private final List<ClassfileBytecode> codeAttributes;
private static final int MAJOR_VERSION_JAVA7 = 51;
private static final int MAJOR_VERSION_JAVA9 = 53;
public Classfile(ResolvedJavaType type, DataInputStream stream, ClassfileBytecodeProvider context) throws IOException {
this.type = type;
int magic = stream.readInt();
assert magic == 0xCAFEBABE;
int minor = stream.readUnsignedShort();
int major = stream.readUnsignedShort();
if (major < MAJOR_VERSION_JAVA7 || major > MAJOR_VERSION_JAVA9) {
throw new UnsupportedClassVersionError("Unsupported class file version: " + major + "." + minor);
}
ClassfileConstantPool cp = new ClassfileConstantPool(stream, context);
skipFully(stream, 6);
skipFully(stream, stream.readUnsignedShort() * 2);
skipFields(stream);
codeAttributes = readMethods(stream, cp);
skipAttributes(stream);
}
public ClassfileBytecode getCode(String name, String descriptor) {
for (ClassfileBytecode code : codeAttributes) {
ResolvedJavaMethod method = code.getMethod();
if (method.getName().equals(name) && method.getSignature().toMethodDescriptor().equals(descriptor)) {
return code;
}
}
throw new NoSuchMethodError(type.toJavaName() + "." + name + descriptor);
}
private static void skipAttributes(DataInputStream stream) throws IOException {
int attributesCount;
attributesCount = stream.readUnsignedShort();
for (int i = 0; i < attributesCount; i++) {
skipFully(stream, 2);
int attributeLength = stream.readInt();
skipFully(stream, attributeLength);
}
}
static void skipFully(DataInputStream stream, int n) throws IOException {
long skipped = 0;
do {
long s = stream.skip(n - skipped);
skipped += s;
if (s == 0 && skipped != n) {
if (stream.read() == -1) {
throw new IOException("truncated stream");
}
skipped++;
}
} while (skipped != n);
}
private ClassfileBytecode findCodeAttribute(DataInputStream stream, ClassfileConstantPool cp, String name, String descriptor, boolean isStatic) throws IOException {
int attributesCount;
attributesCount = stream.readUnsignedShort();
ClassfileBytecode code = null;
for (int i = 0; i < attributesCount; i++) {
String attributeName = cp.get(Utf8.class, stream.readUnsignedShort()).value;
int attributeLength = stream.readInt();
if (code == null && attributeName.equals("Code")) {
ResolvedJavaMethod method = ClassfileBytecodeProvider.findMethod(type, name, descriptor, isStatic);
code = new ClassfileBytecode(method, stream, cp);
if (method == null) {
code = null;
}
} else {
skipFully(stream, attributeLength);
}
}
return code;
}
private static void skipFields(DataInputStream stream) throws IOException {
int count = stream.readUnsignedShort();
for (int i = 0; i < count; i++) {
skipFully(stream, 6);
skipAttributes(stream);
}
}
private List<ClassfileBytecode> readMethods(DataInputStream stream, ClassfileConstantPool cp) throws IOException {
int count = stream.readUnsignedShort();
List<ClassfileBytecode> result = new ArrayList<>(count);
for (int i = 0; i < count; i++) {
int accessFlags = stream.readUnsignedShort();
boolean isStatic = Modifier.isStatic(accessFlags);
String name = cp.get(Utf8.class, stream.readUnsignedShort()).value;
String descriptor = cp.get(Utf8.class, stream.readUnsignedShort()).value;
ClassfileBytecode code = findCodeAttribute(stream, cp, name, descriptor, isStatic);
if (code != null) {
result.add(code);
}
}
return result;
}
@Override
public String toString() {
return getClass().getSimpleName() + "<" + type.toJavaName() + ">";
}
}