improved java bytecode reader (nearly complete)

This commit is contained in:
Sander Hautvast 2020-07-17 17:23:16 +02:00
parent 78940c4469
commit 0763ad1fd6
18 changed files with 396 additions and 262 deletions

View file

@ -0,0 +1,12 @@
package nl.sander.jsontoy2;
public class AccesModifiers {
public final static int ACC_PUBLIC = 0x0001; // Declared public; may be accessed from outside its package.
public final static int ACC_FINAL = 0x0010; // Declared final; no subclasses allowed.
public final static int ACC_SUPER = 0x0020; // Treat superclass methods specially when invoked by the invokespecial instruction.
public final static int ACC_INTERFACE = 0x0200; // Is an interface, not a class.
public final static int ACC_ABSTRACT = 0x0400; // Declared abstract; must not be instantiated.
public final static int ACC_SYNTHETIC = 0x1000; // Declared synthetic; not present in the source code.
public final static int ACC_ANNOTATION = 0x2000; // Declared as an annotation type.
public final static int ACC_ENUM = 0x4000; // Declared as an enum type.
}

View file

@ -0,0 +1,11 @@
package nl.sander.jsontoy2.java;
public class AttributeInfo {
private final int nameIndex;
private final byte[] info;
public AttributeInfo(int nameIndex, byte[] info) {
this.nameIndex = nameIndex;
this.info = info;
}
}

View file

@ -1,6 +1,7 @@
package nl.sander.jsontoy2.java;
import nl.sander.jsontoy2.java.constantpool.*;
import nl.sander.jsontoy2.java.constantpool.ConstantPoolEntry;
import nl.sander.jsontoy2.java.constantpool.Utf8Entry;
import java.util.Arrays;
import java.util.Set;
@ -9,116 +10,73 @@ import java.util.stream.Collectors;
public class ClassObject<T> {
private int constantPoolCount;
private ConstantPoolEntry[] constantPool;
private int constantPoolIndex = 0;
private Info[] fieldInfos;
private Info[] methodInfos;
public int getConstantPoolCount() {
return constantPoolCount;
private String getUtf8(int index) {
return ((Utf8Entry) constantPool[index - 1]).getUtf8();
}
public Set<Field> getFields() {
return Arrays.stream(fieldInfos)
.map(fi -> new Field(getUtf8(fi.getNameIndex()), getUtf8(fi.getDescriptorIndex())))
.collect(Collectors.toSet());
}
public Set<Method> getMethods() {
return Arrays.stream(methodInfos)
.map(mi -> new Method(getUtf8(mi.getNameIndex()), getUtf8(mi.getDescriptorIndex())))
.collect(Collectors.toSet());
}
public String toString() {
StringBuilder builder = new StringBuilder();
for (int i = 0; i < constantPoolCount - 1; i++) {
ConstantPoolEntry entry = constantPool[i];
for (ConstantPoolEntry entry : constantPool) {
builder.append(entry.toString());
builder.append(String.format("%n"));
}
return builder.toString();
}
private void add(ConstantPoolEntry entry) {
constantPool[constantPoolIndex++] = entry;
}
public Set<Field> getFields() {
return Arrays.stream(constantPool)
.filter(e -> e instanceof FieldRefEntry)
.map(FieldRefEntry.class::cast)
.map(f -> {
NameAndType nat = getNameAndType(f);
return new Field(nat.getName(), nat.getType());
})
.collect(Collectors.toSet());
}
private NameAndType getNameAndType(FieldRefEntry f) {
NameAndTypeEntry natEntry = (NameAndTypeEntry) constantPool[f.getNameAndTypeIndex() - 1];
return new NameAndType(getUtf8(natEntry.getNameIndex()), getUtf8(natEntry.getTypeIndex()));
}
private String getUtf8(short index) {
return ((Utf8Entry) constantPool[index - 1]).getUtf8();
}
public static class Builder<T> {
private final ClassObject<T> classObject = new ClassObject<>();
private int constantPoolIndex = 0;
private int fieldInfoIndex = 0;
private int methodInfoIndex = 0;
public ClassObject<T> build() {
return classObject;
}
public Builder<T> constantPoolCount(int constantPoolCount) {
classObject.constantPoolCount = constantPoolCount;
classObject.constantPool = new ConstantPoolEntry[constantPoolCount];
classObject.constantPool = new ConstantPoolEntry[constantPoolCount - 1];
return this;
}
public void constantPoolEntry(String utf8) {
classObject.add(new Utf8Entry(utf8));
void constantPoolEntry(ConstantPoolEntry entry) {
classObject.constantPool[constantPoolIndex++] = entry;
}
public void constantPoolEntry(int i) {
classObject.add(new IntEntry(i));
public Builder<T> fieldInfoCount(int fieldInfoCount) {
classObject.fieldInfos = new Info[fieldInfoCount];
return this;
}
public void constantPoolEntry(float f) {
classObject.add(new FloatEntry(f));
public Builder<T> fieldInfo(Info fieldInfo) {
classObject.fieldInfos[fieldInfoIndex++] = fieldInfo;
return this;
}
public void constantPoolEntry(long f) {
classObject.add(new LongEntry(f));
public Builder<T> methodInfoCount(int methodInfoCount) {
classObject.methodInfos = new Info[methodInfoCount];
return this;
}
public void constantPoolEntry(double d) {
classObject.add(new DoubleEntry(d));
}
public void constantPoolClassEntry(short nameIndex) {
classObject.add(new ClassEntry(nameIndex));
}
public void constantPoolStringEntry(short utf8Index) {
classObject.add(new StringEntry(utf8Index));
}
public void constantPoolFieldRefEntry(short classIndex, short nameAndTypeIndex) {
classObject.add(new FieldRefEntry(classIndex, nameAndTypeIndex));
}
public void constantPoolMethodRefEntry(short classIndex, short nameAndTypeIndex) {
classObject.add(new MethodRefEntry(classIndex, nameAndTypeIndex));
}
public void constantPoolInterfaceMethodRefEntry(short classIndex, short nameAndTypeIndex) {
classObject.add(new InterfaceMethodRefEntry(classIndex, nameAndTypeIndex));
}
public void constantPoolNameAndTypeEntry(short nameIndex, short typeIndex) {
classObject.add(new NameAndTypeEntry(nameIndex, typeIndex));
}
public void constantPoolMethodHandleEntry(short referenceKind, short referenceIndex) {
classObject.add(new MethodHandleEntry(referenceKind, referenceIndex));
}
public void constantPoolMethodTypeEntry(short descriptorIndex) {
classObject.add(new MethodTypeEntry(descriptorIndex));
}
public void constantPoolInvokeDynamicEntry(short bootstrapMethodAttrIndex, short nameAndTypeIndex) {
classObject.add(new InvokeDynamicEntry(bootstrapMethodAttrIndex, nameAndTypeIndex));
public Builder<T> methodInfo(Info methodInfo) {
classObject.methodInfos[methodInfoIndex++] = methodInfo;
return this;
}
}
}

View file

@ -1,152 +0,0 @@
package nl.sander.jsontoy2.java;
import java.io.BufferedInputStream;
import java.io.DataInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
public class ClassParser {
public <T> ClassObject<T> parse(Class<T> type) {
DataInputStream in = new DataInputStream(new BufferedInputStream(type.getResourceAsStream(getResourceName(type))));
expect(in, 0xCAFEBABE);
ClassObject.Builder<T> builder = new ClassObject.Builder<>();
skip(in, 4); //skip version
int constantPoolCount = readShort(in);
builder.constantPoolCount(constantPoolCount);
for (int i = 1; i < constantPoolCount; i++) {
readConstantPoolEntry(in, builder);
}
return builder.build();
}
private <T> void readConstantPoolEntry(DataInputStream in, ClassObject.Builder<T> builder) {
byte tag = readByte(in);
switch (tag) {
case 1: readUtf8(in, builder);
break;
case 2: throw new IllegalStateException("2: invalid classpool tag");
case 3: builder.constantPoolEntry(readInt(in));
break;
case 4: builder.constantPoolEntry(readFloat(in));
break;
case 5: builder.constantPoolEntry(readLong(in));
break;
case 6: builder.constantPoolEntry(readDouble(in));
break;
case 7: builder.constantPoolClassEntry(readShort(in));
break;
case 8: builder.constantPoolStringEntry(readShort(in));
break;
case 9: builder.constantPoolFieldRefEntry(readShort(in), readShort(in));
break;
case 10: builder.constantPoolMethodRefEntry(readShort(in), readShort(in));
break;
case 11: builder.constantPoolInterfaceMethodRefEntry(readShort(in), readShort(in));
break;
case 12: builder.constantPoolNameAndTypeEntry(readShort(in), readShort(in));
break;
case 15: builder.constantPoolMethodHandleEntry(readShort(in), readShort(in));
break;
case 16: builder.constantPoolMethodTypeEntry(readShort(in));
break;
case 18: builder.constantPoolInvokeDynamicEntry(readShort(in), readShort(in));
break;
}
}
private void readUtf8(DataInputStream in, ClassObject.Builder<?> builder) {
short length = readShort(in);
String utf8 = readString(in, length);
builder.constantPoolEntry(utf8);
}
private String readString(DataInputStream in, short length) {
try {
byte[] bytes = in.readNBytes(length);
return new String(bytes, StandardCharsets.UTF_8);
} catch (IOException e) {
throw new RuntimeException();
}
}
private long skip(InputStream in, long bytecount) {
try {
return in.skip(bytecount);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
private byte readByte(DataInputStream in) {
try {
return in.readByte();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
private short readShort(DataInputStream in) {
try {
return in.readShort();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
private int readInt(DataInputStream in) {
try {
return in.readInt();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
private float readFloat(DataInputStream in) {
try {
return in.readFloat();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
private long readLong(DataInputStream in) {
try {
return in.readLong();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
private double readDouble(DataInputStream in) {
try {
return in.readDouble();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
private void expect(DataInputStream in, int expected) {
try {
int i = in.readInt();
if (i != expected) {
throw new IllegalStateException("class file not valid");
}
} catch (IOException e) {
throw new RuntimeException(e);
}
}
private <T> String getResourceName(Class<T> type) {
StringBuilder typeName = new StringBuilder("/" + type.getName());
for (int i = 0; i < typeName.length(); i++) {
if (typeName.charAt(i) == '.') {
typeName.setCharAt(i, '/');
}
}
typeName.append(".class");
return typeName.toString();
}
}

View file

@ -0,0 +1,123 @@
package nl.sander.jsontoy2.java;
import nl.sander.jsontoy2.java.constantpool.*;
import java.io.BufferedInputStream;
import java.io.DataInputStream;
import java.io.IOException;
public class ClassReader extends DataReader {
public <T> ClassObject<T> parse(Class<T> type) {
DataInputStream in = new DataInputStream(new BufferedInputStream(type.getResourceAsStream(getResourceName(type))));
expect(in, 0xCAFEBABE);
ClassObject.Builder<T> builder = new ClassObject.Builder<>();
skip(in, 4); // u2 minor_version, u2 major_version
readConstantPool(in, builder);
skip(in, 6); // u2 access_flags, u2 this_class, u2 super_class
int interfacesCount = readUnsignedShort(in);
skip(in, interfacesCount * 2); // interfaces[]
readFields(in, builder);
readMethods(in, builder);
return builder.build();
}
private <T> void readConstantPool(DataInputStream in, ClassObject.Builder<T> builder) {
int constantPoolCount = readUnsignedShort(in);
builder.constantPoolCount(constantPoolCount);
for (int i = 1; i < constantPoolCount; i++) {
builder.constantPoolEntry(readConstantPoolEntry(in));
}
}
private <T> void readFields(DataInputStream in, ClassObject.Builder<T> builder) {
int fieldInfoCount = readUnsignedShort(in);
builder.fieldInfoCount(fieldInfoCount);
for (int i = 0; i < fieldInfoCount; i++) {
builder.fieldInfo(readField(in));
}
}
private <T> void readMethods(DataInputStream in, ClassObject.Builder<T> builder) {
int methodInfoCount = readUnsignedShort(in);
builder.methodInfoCount(methodInfoCount);
for (int i = 0; i < methodInfoCount; i++) {
builder.methodInfo(readMethod(in));
}
}
private <T> Info readField(DataInputStream in) {
Info fieldInfo = new Info(readUnsignedShort(in), readUnsignedShort(in), readUnsignedShort(in), readUnsignedShort(in));
for (int i = 0; i < fieldInfo.getAttributesCount(); i++) {
fieldInfo.add(readAttribute(in));
}
return fieldInfo;
}
private <T> Info readMethod(DataInputStream in) {
Info methodInfo = new Info(readUnsignedShort(in), readUnsignedShort(in), readUnsignedShort(in), readUnsignedShort(in));
for (int i = 0; i < methodInfo.getAttributesCount(); i++) {
methodInfo.add(readAttribute(in));
}
return methodInfo;
}
private AttributeInfo readAttribute(DataInputStream in) {
int attributeNameIndex = readUnsignedShort(in);
int attributeLength = readInt(in);
byte[] info;
if (attributeLength > 0) {
info = new byte[attributeLength];
try {
in.readFully(info);
} catch (IOException e) {
throw new RuntimeException(e);
}
} else {
info = new byte[0];
}
return new AttributeInfo(attributeNameIndex, info);
}
private <T> ConstantPoolEntry readConstantPoolEntry(DataInputStream in) {
byte tag = readByte(in);
switch (tag) {
case 1: return readUtf8Entry(in);
case 2: throw new IllegalStateException("2: invalid classpool tag");
case 3: return new IntEntry(readInt(in));
case 4: return new FloatEntry(readFloat(in));
case 5: return new LongEntry(readLong(in));
case 6: return new DoubleEntry(readDouble(in));
case 7: return new ClassEntry(readUnsignedShort(in));
case 8: return new StringEntry(readUnsignedShort(in));
case 9: return new FieldRefEntry(readShort(in), readShort(in));
case 10: return new MethodRefEntry(readUnsignedShort(in), readUnsignedShort(in));
case 11: return new InterfaceMethodRefEntry(readUnsignedShort(in), readUnsignedShort(in));
case 12: return new NameAndTypeEntry(readUnsignedShort(in), readUnsignedShort(in));
case 15: return new MethodHandleEntry(readUnsignedShort(in), readUnsignedShort(in));
case 16: return new MethodTypeEntry(readUnsignedShort(in));
case 18: return new InvokeDynamicEntry(readUnsignedShort(in), readUnsignedShort(in));
default: throw new IllegalStateException("invalid classpool");
}
}
protected <T> String getResourceName(Class<T> type) {
StringBuilder typeName = new StringBuilder("/" + type.getName());
for (int i = 0; i < typeName.length(); i++) {
if (typeName.charAt(i) == '.') {
typeName.setCharAt(i, '/');
}
}
typeName.append(".class");
return typeName.toString();
}
}

View file

@ -0,0 +1,100 @@
package nl.sander.jsontoy2.java;
import nl.sander.jsontoy2.java.constantpool.Utf8Entry;
import java.io.DataInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
public class DataReader {
protected float readFloat(DataInputStream in) {
try {
return in.readFloat();
} catch (IOException e) {
throw new RuntimeException();
}
}
protected Utf8Entry readUtf8Entry(DataInputStream in) {
short length = readShort(in);
return new Utf8Entry(readString(in, length));
}
protected String readString(DataInputStream in, short length) {
try {
byte[] bytes = in.readNBytes(length);
return new String(bytes, StandardCharsets.UTF_8);
} catch (IOException e) {
throw new RuntimeException();
}
}
protected long readLong(DataInputStream in) {
try {
return in.readLong();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
protected double readDouble(DataInputStream in) {
try {
return in.readDouble();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
protected short readShort(DataInputStream in) {
try {
return in.readShort();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
protected int readUnsignedShort(DataInputStream in) {
try {
return in.readUnsignedShort();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
protected int readInt(DataInputStream in) {
try {
return in.readInt();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
protected byte readByte(DataInputStream in) {
try {
return in.readByte();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
protected void expect(DataInputStream in, int expected) {
try {
int i = in.readInt();
if (i != expected) {
throw new IllegalStateException("class file not valid");
}
} catch (IOException e) {
throw new RuntimeException(e);
}
}
protected long skip(InputStream in, long bytecount) {
try {
return in.skip(bytecount);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}

View file

@ -41,4 +41,6 @@ public class Field {
", type='" + type + '\'' +
'}';
}
}

View file

@ -0,0 +1,36 @@
package nl.sander.jsontoy2.java;
import java.util.HashSet;
import java.util.Set;
public class Info {
private final int accesFlags;
private final int nameIndex;
private final int descriptorIndex;
private final int attributesCount;
private final Set<AttributeInfo> attributeInfos = new HashSet<>();
public Info(int accesFlags, int nameIndex, int descriptorIndex, int attributesCount) {
this.accesFlags = accesFlags;
this.nameIndex = nameIndex;
this.descriptorIndex = descriptorIndex;
this.attributesCount = attributesCount;
}
public int getNameIndex() {
return nameIndex;
}
public int getDescriptorIndex() {
return descriptorIndex;
}
void add(AttributeInfo attributeInfo) {
attributeInfos.add(attributeInfo);
}
public int getAttributesCount() {
return attributesCount;
}
}

View file

@ -0,0 +1,44 @@
package nl.sander.jsontoy2.java;
import java.util.Objects;
public class Method {
private final String name;
private final String type;
public Method(String name, String type) {
this.name = name;
this.type = type;
}
public String getName() {
return name;
}
public String getType() {
return type;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Method field = (Method) o;
return name.equals(field.name) &&
type.equals(field.type);
}
@Override
public int hashCode() {
return Objects.hash(name, type);
}
@Override
public String toString() {
return "Method{" +
"name='" + name + '\'' +
", type='" + type + '\'' +
'}';
}
}

View file

@ -1,9 +1,9 @@
package nl.sander.jsontoy2.java.constantpool;
public class ClassEntry extends ConstantPoolEntry {
private final short nameIndex;
private final int nameIndex;
public ClassEntry(short nameIndex) {
public ClassEntry(int nameIndex) {
this.nameIndex = nameIndex;
}

View file

@ -1,10 +1,10 @@
package nl.sander.jsontoy2.java.constantpool;
public class InterfaceMethodRefEntry extends ConstantPoolEntry {
private final short classIndex;
private final short nameAndTypeIndex;
private final int classIndex;
private final int nameAndTypeIndex;
public InterfaceMethodRefEntry(short classIndex, short nameAndTypeIndex) {
public InterfaceMethodRefEntry(int classIndex, int nameAndTypeIndex) {
this.classIndex = classIndex;
this.nameAndTypeIndex = nameAndTypeIndex;
}

View file

@ -1,10 +1,10 @@
package nl.sander.jsontoy2.java.constantpool;
public class InvokeDynamicEntry extends ConstantPoolEntry {
private final short bootstrapMethodAttrIndex;
private final short nameAndTypeIndex;
private final int bootstrapMethodAttrIndex;
private final int nameAndTypeIndex;
public InvokeDynamicEntry(short bootstrapMethodAttrIndex, short nameAndTypeIndex) {
public InvokeDynamicEntry(int bootstrapMethodAttrIndex, int nameAndTypeIndex) {
this.bootstrapMethodAttrIndex = bootstrapMethodAttrIndex;
this.nameAndTypeIndex = nameAndTypeIndex;
}

View file

@ -1,10 +1,10 @@
package nl.sander.jsontoy2.java.constantpool;
public class MethodHandleEntry extends ConstantPoolEntry {
private final short referenceKind;
private final short referenceIndex;
private final int referenceKind;
private final int referenceIndex;
public MethodHandleEntry(short referenceKind, short referenceIndex) {
public MethodHandleEntry(int referenceKind, int referenceIndex) {
this.referenceKind = referenceKind;
this.referenceIndex = referenceIndex;
}

View file

@ -1,14 +1,18 @@
package nl.sander.jsontoy2.java.constantpool;
public class MethodRefEntry extends ConstantPoolEntry {
private final short classIndex;
private final short nameAndTypeIndex;
private final int classIndex;
private final int nameAndTypeIndex;
public MethodRefEntry(short classIndex, short nameAndTypeIndex) {
public MethodRefEntry(int classIndex, int nameAndTypeIndex) {
this.classIndex = classIndex;
this.nameAndTypeIndex = nameAndTypeIndex;
}
public int getNameAndTypeIndex() {
return nameAndTypeIndex;
}
@Override
public String toString() {
return "MethodRefEntry{" +

View file

@ -1,9 +1,9 @@
package nl.sander.jsontoy2.java.constantpool;
public class MethodTypeEntry extends ConstantPoolEntry {
private final short descriptorIndex;
private final int descriptorIndex;
public MethodTypeEntry(short descriptorIndex) {
public MethodTypeEntry(int descriptorIndex) {
this.descriptorIndex = descriptorIndex;
}

View file

@ -1,19 +1,19 @@
package nl.sander.jsontoy2.java.constantpool;
public class NameAndTypeEntry extends ConstantPoolEntry {
private final short nameIndex;
private final short typeIndex;
private final int nameIndex;
private final int typeIndex;
public NameAndTypeEntry(short nameIndex, short typeIndex) {
public NameAndTypeEntry(int nameIndex, int typeIndex) {
this.nameIndex = nameIndex;
this.typeIndex = typeIndex;
}
public short getNameIndex() {
public int getNameIndex() {
return nameIndex;
}
public short getTypeIndex() {
public int getTypeIndex() {
return typeIndex;
}

View file

@ -1,9 +1,9 @@
package nl.sander.jsontoy2.java.constantpool;
public class StringEntry extends ConstantPoolEntry {
private final short utf8Index;
private final int utf8Index;
public StringEntry(short utf8Index) {
public StringEntry(int utf8Index) {
this.utf8Index = utf8Index;
}

View file

@ -6,18 +6,14 @@ import java.util.Set;
import static org.junit.jupiter.api.Assertions.assertEquals;
public class ClassParserTest {
public class ClassReaderTest {
private int field;
public int field;
@Test
public void testReadClass() {
ClassObject<ClassParserTest> object = new ClassParser().parse(ClassParserTest.class);
ClassObject<ClassReaderTest> object = new ClassReader().parse(ClassReaderTest.class);
assertEquals(Set.of(new Field("field", "I")), object.getFields());
}
// if not included, field is not in the compiled code.
public int getField() {
return field;
assertEquals(Set.of(new Method("<init>", "()V"), new Method("testReadClass", "()V")), object.getMethods());
}
}