added java bytecode reader (not complete yet)

This commit is contained in:
Sander Hautvast 2020-07-16 19:18:29 +02:00
parent fd62bc9167
commit 78940c4469
20 changed files with 631 additions and 0 deletions

View file

@ -0,0 +1,124 @@
package nl.sander.jsontoy2.java;
import nl.sander.jsontoy2.java.constantpool.*;
import java.util.Arrays;
import java.util.Set;
import java.util.stream.Collectors;
public class ClassObject<T> {
private int constantPoolCount;
private ConstantPoolEntry[] constantPool;
private int constantPoolIndex = 0;
public int getConstantPoolCount() {
return constantPoolCount;
}
public String toString() {
StringBuilder builder = new StringBuilder();
for (int i = 0; i < constantPoolCount - 1; i++) {
ConstantPoolEntry entry = constantPool[i];
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<>();
public ClassObject<T> build() {
return classObject;
}
public Builder<T> constantPoolCount(int constantPoolCount) {
classObject.constantPoolCount = constantPoolCount;
classObject.constantPool = new ConstantPoolEntry[constantPoolCount];
return this;
}
public void constantPoolEntry(String utf8) {
classObject.add(new Utf8Entry(utf8));
}
public void constantPoolEntry(int i) {
classObject.add(new IntEntry(i));
}
public void constantPoolEntry(float f) {
classObject.add(new FloatEntry(f));
}
public void constantPoolEntry(long f) {
classObject.add(new LongEntry(f));
}
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));
}
}
}

View file

@ -0,0 +1,152 @@
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,44 @@
package nl.sander.jsontoy2.java;
import java.util.Objects;
public class Field {
private final String name;
private final String type;
public Field(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;
Field field = (Field) o;
return name.equals(field.name) &&
type.equals(field.type);
}
@Override
public int hashCode() {
return Objects.hash(name, type);
}
@Override
public String toString() {
return "Field{" +
"name='" + name + '\'' +
", type='" + type + '\'' +
'}';
}
}

View file

@ -0,0 +1,16 @@
package nl.sander.jsontoy2.java.constantpool;
public class ClassEntry extends ConstantPoolEntry {
private final short nameIndex;
public ClassEntry(short nameIndex) {
this.nameIndex = nameIndex;
}
@Override
public String toString() {
return "ClassEntry{" +
"nameIndex=" + nameIndex +
'}';
}
}

View file

@ -0,0 +1,5 @@
package nl.sander.jsontoy2.java.constantpool;
public abstract class ConstantPoolEntry {
}

View file

@ -0,0 +1,16 @@
package nl.sander.jsontoy2.java.constantpool;
public class DoubleEntry extends ConstantPoolEntry {
private final double doubleVal;
public DoubleEntry(double doubleVal) {
this.doubleVal = doubleVal;
}
@Override
public String toString() {
return "DoubleEntry{" +
"doubleVal=" + doubleVal +
'}';
}
}

View file

@ -0,0 +1,27 @@
package nl.sander.jsontoy2.java.constantpool;
public class FieldRefEntry extends ConstantPoolEntry {
private final short classIndex;
private final short nameAndTypeIndex;
public FieldRefEntry(short classIndex, short nameAndTypeIndex) {
this.classIndex = classIndex;
this.nameAndTypeIndex = nameAndTypeIndex;
}
public short getClassIndex() {
return classIndex;
}
public short getNameAndTypeIndex() {
return nameAndTypeIndex;
}
@Override
public String toString() {
return "FieldRefEntry{" +
"classIndex=" + classIndex +
", nameAndTypeIndex=" + nameAndTypeIndex +
'}';
}
}

View file

@ -0,0 +1,16 @@
package nl.sander.jsontoy2.java.constantpool;
public class FloatEntry extends ConstantPoolEntry {
private final float floatVal;
public FloatEntry(float floatVal) {
this.floatVal = floatVal;
}
@Override
public String toString() {
return "FloatEntry{" +
"floatVal=" + floatVal +
'}';
}
}

View file

@ -0,0 +1,16 @@
package nl.sander.jsontoy2.java.constantpool;
public class IntEntry extends ConstantPoolEntry {
private final int intVal;
public IntEntry(int integer) {
this.intVal = integer;
}
@Override
public String toString() {
return "IntEntry{" +
"intVal=" + intVal +
'}';
}
}

View file

@ -0,0 +1,19 @@
package nl.sander.jsontoy2.java.constantpool;
public class InterfaceMethodRefEntry extends ConstantPoolEntry {
private final short classIndex;
private final short nameAndTypeIndex;
public InterfaceMethodRefEntry(short classIndex, short nameAndTypeIndex) {
this.classIndex = classIndex;
this.nameAndTypeIndex = nameAndTypeIndex;
}
@Override
public String toString() {
return "InterfaceMethodRefEntry{" +
"classIndex=" + classIndex +
", nameAndTypeIndex=" + nameAndTypeIndex +
'}';
}
}

View file

@ -0,0 +1,19 @@
package nl.sander.jsontoy2.java.constantpool;
public class InvokeDynamicEntry extends ConstantPoolEntry {
private final short bootstrapMethodAttrIndex;
private final short nameAndTypeIndex;
public InvokeDynamicEntry(short bootstrapMethodAttrIndex, short nameAndTypeIndex) {
this.bootstrapMethodAttrIndex = bootstrapMethodAttrIndex;
this.nameAndTypeIndex = nameAndTypeIndex;
}
@Override
public String toString() {
return "InvokeDynamicEntry{" +
"bootstrapMethodAttrIndex=" + bootstrapMethodAttrIndex +
", nameAndTypeIndex=" + nameAndTypeIndex +
'}';
}
}

View file

@ -0,0 +1,17 @@
package nl.sander.jsontoy2.java.constantpool;
public class LongEntry extends ConstantPoolEntry {
private final long longVal;
public LongEntry(long longVal) {
this.longVal = longVal;
}
@Override
public String toString() {
return "LongEntry{" +
"longVal=" + longVal +
'}';
}
}

View file

@ -0,0 +1,19 @@
package nl.sander.jsontoy2.java.constantpool;
public class MethodHandleEntry extends ConstantPoolEntry {
private final short referenceKind;
private final short referenceIndex;
public MethodHandleEntry(short referenceKind, short referenceIndex) {
this.referenceKind = referenceKind;
this.referenceIndex = referenceIndex;
}
@Override
public String toString() {
return "MethodHandleEntry{" +
"referenceKind=" + referenceKind +
", referenceIndex=" + referenceIndex +
'}';
}
}

View file

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

View file

@ -0,0 +1,16 @@
package nl.sander.jsontoy2.java.constantpool;
public class MethodTypeEntry extends ConstantPoolEntry {
private final short descriptorIndex;
public MethodTypeEntry(short descriptorIndex) {
this.descriptorIndex = descriptorIndex;
}
@Override
public String toString() {
return "MethodTypeEntry{" +
"descriptorIndex=" + descriptorIndex +
'}';
}
}

View file

@ -0,0 +1,19 @@
package nl.sander.jsontoy2.java.constantpool;
public class NameAndType {
private final String name;
private final String type;
public NameAndType(String name, String type) {
this.name = name;
this.type = type;
}
public String getName() {
return name;
}
public String getType() {
return type;
}
}

View file

@ -0,0 +1,27 @@
package nl.sander.jsontoy2.java.constantpool;
public class NameAndTypeEntry extends ConstantPoolEntry {
private final short nameIndex;
private final short typeIndex;
public NameAndTypeEntry(short nameIndex, short typeIndex) {
this.nameIndex = nameIndex;
this.typeIndex = typeIndex;
}
public short getNameIndex() {
return nameIndex;
}
public short getTypeIndex() {
return typeIndex;
}
@Override
public String toString() {
return "NameAndTypeEntry{" +
"nameIndex=" + nameIndex +
", typeIndex=" + typeIndex +
'}';
}
}

View file

@ -0,0 +1,16 @@
package nl.sander.jsontoy2.java.constantpool;
public class StringEntry extends ConstantPoolEntry {
private final short utf8Index;
public StringEntry(short utf8Index) {
this.utf8Index = utf8Index;
}
@Override
public String toString() {
return "StringEntry{" +
"utf8Index=" + utf8Index +
'}';
}
}

View file

@ -0,0 +1,21 @@
package nl.sander.jsontoy2.java.constantpool;
public class Utf8Entry extends ConstantPoolEntry {
private final String stringVal;
public Utf8Entry(String utf8) {
this.stringVal = utf8;
}
public String getUtf8() {
return stringVal;
}
@Override
public String toString() {
return "Utf8Entry{" +
"stringVal='" + stringVal + '\'' +
'}';
}
}

View file

@ -0,0 +1,23 @@
package nl.sander.jsontoy2.java;
import org.junit.jupiter.api.Test;
import java.util.Set;
import static org.junit.jupiter.api.Assertions.assertEquals;
public class ClassParserTest {
private int field;
@Test
public void testReadClass() {
ClassObject<ClassParserTest> object = new ClassParser().parse(ClassParserTest.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;
}
}