End-to-end compilation of a trivial class.
This commit is contained in:
parent
ab4662f86e
commit
e59d214a10
33 changed files with 766 additions and 168 deletions
|
|
@ -1,14 +1,12 @@
|
|||
package nl.sander.beejava;
|
||||
|
||||
import nl.sander.beejava.constantpool.ConstantPool;
|
||||
import nl.sander.beejava.constantpool.entry.ConstantPoolEntry;
|
||||
import nl.sander.beejava.flags.AccessFlags;
|
||||
import nl.sander.beejava.util.ByteBuf;
|
||||
|
||||
public class BytecodeGenerator {
|
||||
|
||||
private final CompiledClass compiledClass; // maybe not a member?
|
||||
private final ConstantPoolCreator constantPoolCreator = new ConstantPoolCreator();
|
||||
|
||||
public BytecodeGenerator(CompiledClass compiledClass) {
|
||||
this.compiledClass = compiledClass;
|
||||
|
|
@ -25,10 +23,8 @@ public class BytecodeGenerator {
|
|||
buf.addU16(compiledClass.getSource().getClassFileVersion().getMinor());
|
||||
buf.addU16(compiledClass.getSource().getClassFileVersion().getMajor());
|
||||
|
||||
ConstantPool constantPool = constantPoolCreator.createConstantPool(compiledClass.getConstantTree());
|
||||
|
||||
buf.addU16(constantPool.getLength());
|
||||
constantPool.addTo(buf);
|
||||
buf.addU16(compiledClass.getConstantPool().getLength());
|
||||
compiledClass.getConstantPool().addTo(buf);
|
||||
buf.addU16(AccessFlags.combine(compiledClass.getSource().getAccessFlags()));
|
||||
buf.addU16(compiledClass.geThisIndex());
|
||||
buf.addU16(compiledClass.getSuperIndex());
|
||||
|
|
@ -36,28 +32,10 @@ public class BytecodeGenerator {
|
|||
compiledClass.getInterfaces().forEach(interfase -> buf.addU16(interfase.getIndex()));
|
||||
buf.addU16(compiledClass.getFields().size());
|
||||
compiledClass.getFields().forEach(fieldInfo -> fieldInfo.addBytes(buf));
|
||||
|
||||
int x = 1;
|
||||
for (ConstantPoolEntry e : constantPool) {
|
||||
System.out.println((x++) + ":" + e);
|
||||
}
|
||||
printBytes(buf);
|
||||
|
||||
buf.addU16(compiledClass.getMethods().size());
|
||||
compiledClass.getMethods().forEach(methodInfo -> methodInfo.addBytes(buf));
|
||||
buf.addU16(0); //attributes count
|
||||
return buf.toBytes();
|
||||
}
|
||||
|
||||
//TODO remove
|
||||
private void printBytes(ByteBuf buf) {
|
||||
byte[] bytes = buf.toBytes();
|
||||
int count = 0;
|
||||
for (byte b : bytes) {
|
||||
System.out.print(String.format("%2s", Integer.toHexString(b & 0xFF)).replace(' ', '0') + (count % 2 == 0 ? "" : " "));
|
||||
if (++count > 15) {
|
||||
count = 0;
|
||||
System.out.println();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,9 +1,15 @@
|
|||
package nl.sander.beejava;
|
||||
|
||||
import nl.sander.beejava.api.BeeParameter;
|
||||
import nl.sander.beejava.api.BeeSource;
|
||||
import nl.sander.beejava.api.CodeLine;
|
||||
import nl.sander.beejava.flags.MethodAccessFlags;
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* parent of a constructor or a method.
|
||||
|
|
@ -11,8 +17,41 @@ import java.util.List;
|
|||
public abstract class CodeContainer {
|
||||
|
||||
protected final List<CodeLine> code = new LinkedList<>();
|
||||
protected final Set<BeeParameter> formalParameters = new HashSet<>();
|
||||
protected final Set<MethodAccessFlags> accessFlags = new HashSet<>();
|
||||
private BeeSource owner;
|
||||
|
||||
public List<CodeLine> getCode() {
|
||||
return code;
|
||||
}
|
||||
|
||||
public String getSignature() {
|
||||
return getParametersSignature() + TypeMapper.map(getReturnType());
|
||||
}
|
||||
|
||||
public abstract String getName();
|
||||
|
||||
public abstract Class<?> getReturnType();
|
||||
|
||||
private String getParametersSignature() {
|
||||
return formalParameters.stream()
|
||||
.map(BeeParameter::getType)
|
||||
.map(TypeMapper::map)
|
||||
.collect(Collectors.joining(",", "(", ")"));
|
||||
}
|
||||
|
||||
public Set<MethodAccessFlags> getAccessFlags() {
|
||||
return accessFlags;
|
||||
}
|
||||
|
||||
public BeeSource getOwner() {
|
||||
return owner;
|
||||
}
|
||||
|
||||
public void setOwner(BeeSource beeSource) {
|
||||
if (owner != null) {
|
||||
throw new IllegalStateException("Owner set twice. Sue the developer!");
|
||||
}
|
||||
this.owner = beeSource;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,20 +2,25 @@ package nl.sander.beejava;
|
|||
|
||||
import nl.sander.beejava.api.BeeSource;
|
||||
import nl.sander.beejava.classinfo.FieldInfo;
|
||||
import nl.sander.beejava.classinfo.MethodInfo;
|
||||
import nl.sander.beejava.constantpool.ConstantPool;
|
||||
import nl.sander.beejava.constantpool.entry.ClassEntry;
|
||||
import nl.sander.beejava.constantpool.entry.ConstantPoolEntry;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.HashSet;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.Set;
|
||||
|
||||
class CompiledClass {
|
||||
public class CompiledClass {
|
||||
private final Set<ConstantPoolEntry> constantTree = new LinkedHashSet<>();
|
||||
private final Set<ClassEntry> interfaces = new HashSet<>();
|
||||
private final Set<FieldInfo> fields = new HashSet<>();
|
||||
private final Set<MethodInfo> methods = new HashSet<>();
|
||||
private final BeeSource beeSource;
|
||||
private ClassEntry thisClass;
|
||||
private ClassEntry superClass;
|
||||
private ConstantPool constantPool;
|
||||
|
||||
CompiledClass(BeeSource beeSource) {
|
||||
this.beeSource = beeSource;
|
||||
|
|
@ -68,4 +73,20 @@ class CompiledClass {
|
|||
public Set<FieldInfo> getFields() {
|
||||
return fields;
|
||||
}
|
||||
|
||||
public void addMethod(MethodInfo methodInfo) {
|
||||
methods.add(methodInfo);
|
||||
}
|
||||
|
||||
public Set<MethodInfo> getMethods() {
|
||||
return methods;
|
||||
}
|
||||
|
||||
public void setConstantPool(ConstantPool constantPool) {
|
||||
this.constantPool = constantPool;
|
||||
}
|
||||
|
||||
public ConstantPool getConstantPool() {
|
||||
return constantPool;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,6 +2,12 @@ package nl.sander.beejava;
|
|||
|
||||
import nl.sander.beejava.api.BeeSource;
|
||||
import nl.sander.beejava.api.CodeLine;
|
||||
import nl.sander.beejava.classinfo.MethodInfo;
|
||||
import nl.sander.beejava.constantpool.ConstantPool;
|
||||
import nl.sander.beejava.constantpool.entry.ConstantPoolEntry;
|
||||
import nl.sander.beejava.constantpool.entry.FieldRefEntry;
|
||||
import nl.sander.beejava.constantpool.entry.MethodRefEntry;
|
||||
import nl.sander.beejava.constantpool.entry.Utf8Entry;
|
||||
|
||||
/**
|
||||
* Builds a set of a tree of constant pool entries that refer to each other.
|
||||
|
|
@ -15,6 +21,8 @@ import nl.sander.beejava.api.CodeLine;
|
|||
public class Compiler {
|
||||
private final CompiledClass compiledClass;
|
||||
private final ConstantPoolEntryCreator constantPoolEntryCreator;
|
||||
private final ConstantPoolCreator constantPoolCreator = new ConstantPoolCreator();
|
||||
private Utf8Entry codeAttributeNameEntry;
|
||||
|
||||
/**
|
||||
* construct a compiler object.
|
||||
|
|
@ -41,17 +49,58 @@ public class Compiler {
|
|||
* construct a CompiledClass object that contains all information for generating the bytecode
|
||||
*/
|
||||
public CompiledClass compile() {
|
||||
compiledClass.getSource().getConstructors().forEach(this::updateConstantPool);
|
||||
compiledClass.getSource().getMethods().forEach(this::updateConstantPool);
|
||||
// TODO update constant pool for fields ?
|
||||
compiledClass.setConstantPool(createConstantPool());
|
||||
|
||||
compiledClass.setThisClass(constantPoolEntryCreator.addThisClass());
|
||||
constantPoolEntryCreator.addInterfaces();
|
||||
constantPoolEntryCreator.addFields();
|
||||
addConstructors();
|
||||
addMethods();
|
||||
|
||||
return compiledClass;
|
||||
}
|
||||
|
||||
private ConstantPool createConstantPool() {
|
||||
compiledClass.getSource().getConstructors().forEach(this::updateConstantPool);
|
||||
compiledClass.getSource().getMethods().forEach(this::updateConstantPool); // compiledClass.getSource() ?
|
||||
|
||||
compiledClass.setThisClass(constantPoolEntryCreator.addThisClass());
|
||||
|
||||
this.codeAttributeNameEntry = constantPoolEntryCreator.getOrCreateUtf8("Code");
|
||||
compiledClass.addConstantPoolEntry(codeAttributeNameEntry);
|
||||
|
||||
ConstantPool constantPool = constantPoolCreator.createConstantPool(compiledClass.getConstantTree());
|
||||
return constantPool;
|
||||
}
|
||||
|
||||
public void addConstructors() {
|
||||
compiledClass.getSource().getConstructors().stream()
|
||||
.map(this::createMethod)
|
||||
.forEach(compiledClass::addMethod);
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* maps methods from the source to a MethodInfo and adds that to the CompiledClass.
|
||||
*/
|
||||
public void addMethods() {
|
||||
compiledClass.addConstantPoolEntry(codeAttributeNameEntry);
|
||||
compiledClass.getSource().getMethods().stream()
|
||||
.map(this::createMethod)
|
||||
.forEach(compiledClass::addMethod);
|
||||
}
|
||||
|
||||
/*
|
||||
* create bytecode for method
|
||||
* create methodInfo object for classfile
|
||||
*/
|
||||
private MethodInfo createMethod(CodeContainer method) {
|
||||
return new MethodInfo(
|
||||
constantPoolEntryCreator.getOrCreateUtf8(method.getName()),
|
||||
constantPoolEntryCreator.getOrCreateUtf8(method.getSignature()))
|
||||
.addAccessFlags(method.getAccessFlags())
|
||||
.addAttribute(MethodCodeCreator.createCodeAttribute(codeAttributeNameEntry, method.getCode()));
|
||||
}
|
||||
|
||||
/*
|
||||
* inspect a method or constructor, extract items that need to be added, and add them to the constant pool
|
||||
*/
|
||||
|
|
@ -70,15 +119,21 @@ public class Compiler {
|
|||
*/
|
||||
private void updateConstantPool(CodeLine codeline) {
|
||||
if (codeline.hasMethodCall()) {
|
||||
compiledClass.addConstantPoolEntry(constantPoolEntryCreator.getOrCreateMethodRefEntry(codeline));
|
||||
MethodRefEntry methodRefEntry = constantPoolEntryCreator.getOrCreateMethodRefEntry(codeline);
|
||||
codeline.setAssignedEntry(methodRefEntry);
|
||||
compiledClass.addConstantPoolEntry(methodRefEntry);
|
||||
}
|
||||
|
||||
if (codeline.hasRefToOwnField() || codeline.hasRefToExternalField()) {
|
||||
compiledClass.addConstantPoolEntry(constantPoolEntryCreator.getOrCreateFieldRefEntry(codeline));
|
||||
FieldRefEntry fieldRefEntry = constantPoolEntryCreator.getOrCreateFieldRefEntry(codeline);
|
||||
codeline.setAssignedEntry(fieldRefEntry);
|
||||
compiledClass.addConstantPoolEntry(fieldRefEntry);
|
||||
}
|
||||
|
||||
if (codeline.hasConstValue()) {
|
||||
compiledClass.addConstantPoolEntry(constantPoolEntryCreator.getOrCreatePrimitiveEntry(codeline));
|
||||
ConstantPoolEntry primitiveEntry = constantPoolEntryCreator.getOrCreatePrimitiveEntry(codeline);
|
||||
codeline.setAssignedEntry(primitiveEntry);
|
||||
compiledClass.addConstantPoolEntry(primitiveEntry);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -17,6 +17,19 @@ public class ConstantPoolCreator {
|
|||
return new ConstantPoolCreator().createConstantPool(constantTree);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a ConstantPool from the tree structure provided by the {@link Compiler}
|
||||
*
|
||||
* @param constantTree a Set of CP entries assumed to be laid out correctly
|
||||
* @return a ConstantPool, a list of entries
|
||||
*/
|
||||
/*
|
||||
* resets the current state to create a new constant pool
|
||||
*
|
||||
* This method is halfway at memory efficiency. Next step could be to always have just 1 ConstantPool.
|
||||
* Downsides: no parallelism, maybe keep more track of lingering references
|
||||
*/
|
||||
//@NotThreadSafe
|
||||
public ConstantPool createConstantPool(Set<ConstantPoolEntry> constantTree) {
|
||||
constantPool = new ConstantPool();
|
||||
index = 0;
|
||||
|
|
@ -24,25 +37,34 @@ public class ConstantPoolCreator {
|
|||
return constantPool;
|
||||
}
|
||||
|
||||
/*
|
||||
* javac:
|
||||
* - start with toplevel element (like MethodRef) -> grandparents
|
||||
* - then add direct children -> the parents
|
||||
* - then add grandchildren (I don't think there's more levels)
|
||||
*/
|
||||
private void updateToplevelElements(Set<ConstantPoolEntry> children) {
|
||||
for (ConstantPoolEntry child : children) {
|
||||
addToPool(child);
|
||||
updateChildElements(child.getChildren());
|
||||
// first the complete toplevel element including it's children, then next toplevel element
|
||||
for (ConstantPoolEntry topElement : children) {
|
||||
addToPool(topElement); // grandparents
|
||||
updateChildElements(topElement.getChildren());
|
||||
}
|
||||
}
|
||||
|
||||
private void updateChildElements(Set<ConstantPoolEntry> children) {
|
||||
// first all direct children
|
||||
// parents
|
||||
for (ConstantPoolEntry child : children) {
|
||||
addToPool(child);
|
||||
}
|
||||
// then further lineage
|
||||
// then grandchildren
|
||||
for (ConstantPoolEntry child : children) {
|
||||
updateChildElements(child.getChildren());
|
||||
updateChildElements(child.getChildren()); // no problem if there are great grand children
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* This just adds the next index to an entry and adds the entry to a flat structure.
|
||||
* The tree structure makes sure all references by index will be correct
|
||||
*/
|
||||
private void addToPool(ConstantPoolEntry entry) {
|
||||
index += 1;
|
||||
entry.setIndex(index);
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ package nl.sander.beejava;
|
|||
import nl.sander.beejava.api.CodeLine;
|
||||
import nl.sander.beejava.api.Ref;
|
||||
import nl.sander.beejava.classinfo.FieldInfo;
|
||||
import nl.sander.beejava.classinfo.attributes.CodeAttribute;
|
||||
import nl.sander.beejava.constantpool.entry.*;
|
||||
|
||||
import java.util.HashMap;
|
||||
|
|
@ -20,35 +21,49 @@ class ConstantPoolEntryCreator {
|
|||
this.compiledClass = compiledClass;
|
||||
}
|
||||
|
||||
/*
|
||||
* creates a FieldRefEntry when not found in cache, otherwise gets it from there
|
||||
*/
|
||||
FieldRefEntry getOrCreateFieldRefEntry(CodeLine codeLine) {
|
||||
return cache(new FieldRefEntry(getOrCreateClassEntry(codeLine), createFieldNameAndType(codeLine)));
|
||||
return cache(new FieldRefEntry(getOrCreateClassEntry(codeLine), getOrCreateFieldNameAndType(codeLine)));
|
||||
}
|
||||
|
||||
/*
|
||||
* creates a MethodRefEntry when not found in cache, otherwise gets it from there
|
||||
*/
|
||||
MethodRefEntry getOrCreateMethodRefEntry(CodeLine codeline) {
|
||||
ClassEntry classEntry = getOrCreateClassEntry(codeline);
|
||||
NameAndTypeEntry nameAndType = getOrCreateMethodNameAndType(codeline);
|
||||
return cache(new MethodRefEntry(classEntry, nameAndType));
|
||||
return cache(new MethodRefEntry(classEntry, getOrCreateMethodNameAndType(codeline)));
|
||||
}
|
||||
|
||||
private NameAndTypeEntry createFieldNameAndType(CodeLine codeline) {
|
||||
/*
|
||||
* creates a NamAndTypeEntry for a field when not found in cache, otherwise gets it from there
|
||||
*/
|
||||
private NameAndTypeEntry getOrCreateFieldNameAndType(CodeLine codeline) {
|
||||
if (codeline.hasRefToOwnField()) {
|
||||
return cache(new NameAndTypeEntry(
|
||||
cache(new Utf8Entry(codeline.getOwnfield().getName())),
|
||||
cache(new Utf8Entry(TypeMapper.map(codeline.getOwnfield().getType()))))); // is actually a shortcut
|
||||
} else {
|
||||
} else {//TODO this method may need some work
|
||||
return cache(new NameAndTypeEntry(
|
||||
cache(new Utf8Entry(codeline.getExternalfield())),
|
||||
cache(new Utf8Entry("L" + codeline.getExternalfieldClass()))
|
||||
cache(new Utf8Entry(codeline.getExternalfield().getName())),
|
||||
cache(new Utf8Entry(TypeMapper.map(codeline.getExternalfield().getType())))
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* creates a NamAndTypeEntry for a method when not found in cache, otherwise gets it from there
|
||||
*/
|
||||
private NameAndTypeEntry getOrCreateMethodNameAndType(CodeLine codeline) {
|
||||
return new NameAndTypeEntry(
|
||||
cache(new Utf8Entry(codeline.getMethodName())),
|
||||
cache(new Utf8Entry(codeline.getMethodSignature())));
|
||||
}
|
||||
|
||||
/*
|
||||
* creates a ClassEntry when not found in cache, otherwise gets it from there
|
||||
*/
|
||||
private ClassEntry getOrCreateClassEntry(CodeLine codeline) {
|
||||
if (codeline.hasRef()) {
|
||||
if (codeline.getRef() == Ref.SUPER) { // this and super are rather special
|
||||
|
|
@ -66,12 +81,18 @@ class ConstantPoolEntryCreator {
|
|||
throw new RuntimeException("shouldn't be here");
|
||||
}
|
||||
|
||||
/*
|
||||
* this method gives me a headache. It adds, but also creates a classEntry for the this class
|
||||
*/
|
||||
ClassEntry addThisClass() {
|
||||
ClassEntry classEntry = getClassEntry(compiledClass.getSource().getName());
|
||||
compiledClass.addConstantPoolEntry(classEntry);
|
||||
return classEntry;
|
||||
}
|
||||
|
||||
/*
|
||||
* get or create a ClassEntry
|
||||
*/
|
||||
private ClassEntry getClassEntry(String externalClassName) {
|
||||
return cache(new ClassEntry(cache(new Utf8Entry(internalName(externalClassName)))));
|
||||
}
|
||||
|
|
@ -109,15 +130,24 @@ class ConstantPoolEntryCreator {
|
|||
throw new RuntimeException(); // TODO find out why are you here
|
||||
}
|
||||
|
||||
/*
|
||||
* maps a field from the source to a FieldInfo and adds that to the CompiledClass.
|
||||
*/
|
||||
public void addFields() {
|
||||
compiledClass.getSource().getFields()
|
||||
.forEach(f -> {
|
||||
Utf8Entry name = cache(new Utf8Entry(f.getName()));
|
||||
Utf8Entry descriptor = cache(new Utf8Entry(internalName(f.getType().getName())));
|
||||
compiledClass.addField(new FieldInfo(name, descriptor).addAccessFlags(f.getAccessFlags()));
|
||||
});
|
||||
compiledClass.getSource().getFields().stream()
|
||||
.map(field -> new FieldInfo(
|
||||
cache(new Utf8Entry(field.getName())),
|
||||
cache(new Utf8Entry(internalName(field.getType().getName())))
|
||||
).addAccessFlags(field.getAccessFlags())
|
||||
)
|
||||
.forEach(compiledClass::addField);
|
||||
}
|
||||
|
||||
public Utf8Entry getOrCreateUtf8(String utf8) {
|
||||
return cache(new Utf8Entry(utf8));
|
||||
}
|
||||
|
||||
// why not put this everywhere, it's not like it's ever going to change
|
||||
private String internalName(String name) {
|
||||
return name.replaceAll("\\.", "/");
|
||||
}
|
||||
|
|
@ -132,5 +162,4 @@ class ConstantPoolEntryCreator {
|
|||
// A HashSet is a HashMap with entry: key = value, which would work, but I cannot _get_ anything from a set.
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
|
|
|||
19
src/main/java/nl/sander/beejava/JavaOpcodes.java
Normal file
19
src/main/java/nl/sander/beejava/JavaOpcodes.java
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
package nl.sander.beejava;
|
||||
|
||||
public class JavaOpcodes {
|
||||
public static final int LDC = 0x12;
|
||||
public static final int LDC_W = 0x13;
|
||||
public static final int LDC2_W = 0x14;
|
||||
|
||||
public static final int ALOAD_0 = 0x2a;
|
||||
public static final int RETURN = 0xb1;
|
||||
public static final int GETSTATIC = 0xb2;
|
||||
public static final int GETFIELD = 0xb4;
|
||||
|
||||
public static final int INVOKEINTERFACE = 0xb5;
|
||||
public static final int INVOKEVIRTUAL = 0xb6;
|
||||
public static final int INVOKESPECIAL = 0xb7;
|
||||
public static final int INVOKESTATIC = 0xb8;
|
||||
public static final int INVOKEDYNAMIC = 0xba;
|
||||
|
||||
}
|
||||
28
src/main/java/nl/sander/beejava/MethodCodeCreator.java
Normal file
28
src/main/java/nl/sander/beejava/MethodCodeCreator.java
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
package nl.sander.beejava;
|
||||
|
||||
import nl.sander.beejava.api.CodeLine;
|
||||
import nl.sander.beejava.classinfo.attributes.CodeAttribute;
|
||||
import nl.sander.beejava.constantpool.entry.ConstantPoolEntry;
|
||||
import nl.sander.beejava.constantpool.entry.Utf8Entry;
|
||||
import nl.sander.beejava.util.ByteBuf;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class MethodCodeCreator {
|
||||
|
||||
public static CodeAttribute createCodeAttribute(Utf8Entry codeAttributeNameEntry, List<CodeLine> code) {
|
||||
CodeAttribute codeAttribute = new CodeAttribute(codeAttributeNameEntry);
|
||||
codeAttribute.setMaxStack(1);
|
||||
codeAttribute.setMaxLocals(1);
|
||||
ByteBuf byteBuf = new ByteBuf();
|
||||
code.forEach(codeLine -> {
|
||||
ConstantPoolEntry constantPoolEntry = codeLine.getAssignedEntry();
|
||||
byteBuf.addU8(OpcodeMapper.mapOpcode(codeLine));
|
||||
if (constantPoolEntry != null) {
|
||||
byteBuf.addU16(constantPoolEntry.getIndex());
|
||||
}
|
||||
});
|
||||
codeAttribute.setCode(byteBuf.toBytes());
|
||||
return codeAttribute;
|
||||
}
|
||||
}
|
||||
54
src/main/java/nl/sander/beejava/OpcodeMapper.java
Normal file
54
src/main/java/nl/sander/beejava/OpcodeMapper.java
Normal file
|
|
@ -0,0 +1,54 @@
|
|||
package nl.sander.beejava;
|
||||
|
||||
import nl.sander.beejava.api.BeeSource;
|
||||
import nl.sander.beejava.api.CodeLine;
|
||||
import nl.sander.beejava.api.Opcode;
|
||||
import nl.sander.beejava.flags.ClassAccessFlags;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.Modifier;
|
||||
|
||||
import static nl.sander.beejava.JavaOpcodes.*;
|
||||
|
||||
public class OpcodeMapper {
|
||||
|
||||
public static int mapOpcode(CodeLine codeLine) {
|
||||
Opcode opcode = codeLine.getOpcode();
|
||||
return switch (opcode) {
|
||||
case GET -> isStatic(codeLine.getExternalfield()) ? GETSTATIC : GETFIELD;
|
||||
case LD_VAR -> ALOAD_0;
|
||||
case LD_CONST -> loadConst(codeLine);
|
||||
case INVOKE -> invoke(codeLine);
|
||||
case RETURN -> JavaOpcodes.RETURN; //TODO not complete yet
|
||||
default -> 0;
|
||||
};
|
||||
}
|
||||
|
||||
/* TODO cover more cases */
|
||||
private static int invoke(CodeLine codeLine) {
|
||||
BeeSource source = codeLine.getOwner().getOwner();
|
||||
if (source.getAccessFlags().contains(ClassAccessFlags.SUPER)) {
|
||||
return INVOKESPECIAL;
|
||||
} else {
|
||||
return INVOKEVIRTUAL;
|
||||
}
|
||||
}
|
||||
|
||||
private static int loadConst(CodeLine codeLine) {
|
||||
Object constValue = codeLine.getConstValue();
|
||||
int index = codeLine.getAssignedEntry().getIndex();
|
||||
if (constValue instanceof Double || constValue instanceof Long) {
|
||||
return LDC2_W;
|
||||
} else {
|
||||
if (index > 0xff) {
|
||||
return LDC_W;
|
||||
} else {
|
||||
return LDC;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean isStatic(Field field) {
|
||||
return (field.getModifiers() & Modifier.STATIC) == Modifier.STATIC;
|
||||
}
|
||||
}
|
||||
|
|
@ -16,6 +16,8 @@ public class TypeMapper {
|
|||
MAP.put(long.class, "J");
|
||||
MAP.put(short.class, "S");
|
||||
MAP.put(boolean.class, "Z");
|
||||
MAP.put(Void.class, "V");
|
||||
|
||||
}
|
||||
|
||||
//TODO something with arrays
|
||||
|
|
@ -24,7 +26,7 @@ public class TypeMapper {
|
|||
if (type.isArray()) {
|
||||
return internalName(type.getName());
|
||||
} else {
|
||||
return "L" + internalName(type.getName());
|
||||
return 'L' + internalName(type.getName() + ';');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,8 +9,6 @@ import java.util.*;
|
|||
* Models a constructor
|
||||
*/
|
||||
public class BeeConstructor extends CodeContainer {
|
||||
private final Set<MethodAccessFlags> accessFlags = new HashSet<>();
|
||||
private final Set<BeeParameter> formalParameters = new HashSet<>();
|
||||
|
||||
private BeeConstructor(Set<MethodAccessFlags> accessFlags,
|
||||
List<BeeParameter> formalParameters,
|
||||
|
|
@ -24,12 +22,9 @@ public class BeeConstructor extends CodeContainer {
|
|||
return new Builder();
|
||||
}
|
||||
|
||||
Set<MethodAccessFlags> getAccessFlags() {
|
||||
return accessFlags;
|
||||
}
|
||||
|
||||
Set<BeeParameter> getFormalParameters() {
|
||||
return formalParameters;
|
||||
public String getName() {
|
||||
return "<init>";
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
@ -39,6 +34,10 @@ public class BeeConstructor extends CodeContainer {
|
|||
'}';
|
||||
}
|
||||
|
||||
public Class<?> getReturnType() {
|
||||
return Void.class;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
|
|
@ -77,7 +76,9 @@ public class BeeConstructor extends CodeContainer {
|
|||
}
|
||||
|
||||
public BeeConstructor build() {
|
||||
return new BeeConstructor(accessFlags, formalParameters, code);
|
||||
BeeConstructor beeConstructor = new BeeConstructor(accessFlags, formalParameters, code);
|
||||
code.forEach(line -> line.setOwner(beeConstructor));
|
||||
return beeConstructor;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,18 +1,21 @@
|
|||
package nl.sander.beejava.api;
|
||||
|
||||
import nl.sander.beejava.CodeContainer;
|
||||
import nl.sander.beejava.TypeMapper;
|
||||
import nl.sander.beejava.flags.MethodAccessFlags;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
public final class BeeMethod extends CodeContainer {
|
||||
private final Set<MethodAccessFlags> accessFlags = new HashSet<>();
|
||||
private final Set<BeeParameter> formalParameters = new HashSet<>();
|
||||
private final String name;
|
||||
|
||||
private final Class<?> returnType;
|
||||
|
||||
private BeeMethod(Set<MethodAccessFlags> accessFlags,
|
||||
private BeeMethod(String name, Set<MethodAccessFlags> accessFlags,
|
||||
List<BeeParameter> formalParameters,
|
||||
Class<?> returnType, List<CodeLine> code) {
|
||||
this.name = name;
|
||||
this.accessFlags.addAll(accessFlags);
|
||||
this.formalParameters.addAll(formalParameters);
|
||||
this.returnType = returnType;
|
||||
|
|
@ -23,15 +26,38 @@ public final class BeeMethod extends CodeContainer {
|
|||
return new Builder();
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public Class<?> getReturnType() {
|
||||
return returnType;
|
||||
}
|
||||
|
||||
public void validate() {
|
||||
//TODO
|
||||
/*
|
||||
* here we could add checks like:
|
||||
* -If this method is in a class rather than an interface, and the name of the method is <init>, then the descriptor must denote a void method.
|
||||
* -If the name of the method is <clinit>, then the descriptor must denote avoid method, and, in a class file whose version number is 51.0 or above,a method that takes no arguments
|
||||
*/
|
||||
}
|
||||
|
||||
public static class Builder {
|
||||
private final Set<MethodAccessFlags> accessFlags = new HashSet<>();
|
||||
private final List<BeeParameter> formalParameters = new LinkedList<>();
|
||||
private final List<CodeLine> code = new LinkedList<>();
|
||||
private Class<?> returnType;
|
||||
private String name;
|
||||
private Class<?> returnType = Void.class;
|
||||
|
||||
private Builder() {
|
||||
}
|
||||
|
||||
public Builder withName(String name) {
|
||||
this.name = name;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder withAccessFlags(MethodAccessFlags... accessFlags) {
|
||||
this.accessFlags.addAll(Arrays.asList(accessFlags));
|
||||
return this;
|
||||
|
|
@ -53,7 +79,9 @@ public final class BeeMethod extends CodeContainer {
|
|||
}
|
||||
|
||||
public BeeMethod build() {
|
||||
return new BeeMethod(accessFlags, formalParameters, returnType, code);
|
||||
BeeMethod beeMethod = new BeeMethod(name, accessFlags, formalParameters, returnType, code);
|
||||
code.forEach(line -> line.setOwner(beeMethod));
|
||||
return beeMethod;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -141,7 +141,10 @@ public class BeeSource {
|
|||
}
|
||||
|
||||
public BeeSource build() {
|
||||
return new BeeSource(version, beePackage, accessFlags, simpleName, superClass, interfaces, fields, constructors, methods);
|
||||
BeeSource beeSource = new BeeSource(version, beePackage, accessFlags, simpleName, superClass, interfaces, fields, constructors, methods);
|
||||
constructors.forEach(c -> c.setOwner(beeSource));
|
||||
methods.forEach(m -> m.setOwner(beeSource));
|
||||
return beeSource;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,20 +1,32 @@
|
|||
package nl.sander.beejava.api;
|
||||
|
||||
|
||||
import nl.sander.beejava.CodeContainer;
|
||||
import nl.sander.beejava.TypeMapper;
|
||||
import nl.sander.beejava.constantpool.entry.ConstantPoolEntry;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
public class CodeLine {
|
||||
private final int linenumber;
|
||||
private final Opcode opcode;
|
||||
private Ref ref;
|
||||
private BeeParameter parameter;
|
||||
private String className;
|
||||
private Class<?> type;
|
||||
private String methodName;
|
||||
private String inputSignature;
|
||||
private List<Class<?>> inputSignature;
|
||||
private String outputSignature;
|
||||
private BeeField ownfield; // when you create a class with a field, you can refer to it
|
||||
private String externalfield; // when you refer to a field from another class
|
||||
private Field externalfield; // when you refer to a field from another class
|
||||
|
||||
private Object constValue;
|
||||
private String externalFieldType;
|
||||
|
||||
private ConstantPoolEntry assignedEntry;
|
||||
private CodeContainer owner;
|
||||
|
||||
CodeLine(int linenumber, Opcode opcode) {
|
||||
this.linenumber = linenumber;
|
||||
|
|
@ -33,16 +45,31 @@ public class CodeLine {
|
|||
return new CodeLine(nr, opcode).withConstValue(constValue);
|
||||
}
|
||||
|
||||
public static CodeLine line(int nr, Opcode opcode, String className, String methodName, String inputSignature) {
|
||||
return new CodeLine(nr, opcode).withClassName(className).withMethodName(methodName).withInput(inputSignature).withVoidOutput();
|
||||
public static CodeLine line(int nr, Opcode opcode, String className, String methodName, String inputSignature) throws ClassNotFoundException {
|
||||
return new CodeLine(nr, opcode).withClassName(className).withMethodName(methodName).withInput(parse(inputSignature)).withVoidOutput();
|
||||
}
|
||||
|
||||
public static CodeLine line(int nr, Opcode opcode, Ref ref, String methodNameRef, String inputSignature) {
|
||||
return new CodeLine(nr, opcode).withRef(ref).withMethodName(methodNameRef).withInput(inputSignature).withVoidOutput();
|
||||
private static List<Class<?>> parse(String inputSignature) throws ClassNotFoundException {
|
||||
if ("()".equals(inputSignature)) {
|
||||
return Collections.emptyList();
|
||||
} else {
|
||||
String[] params = inputSignature.split(",");
|
||||
List<Class<?>> paramClasses = new ArrayList<>();
|
||||
for (String param : params) {
|
||||
paramClasses.add(Class.forName(param));
|
||||
}
|
||||
return paramClasses;
|
||||
}
|
||||
}
|
||||
|
||||
public static CodeLine line(int nr, Opcode opcode, Ref ref, String methodNameRef, String inputSignature, String outputSignature) {
|
||||
return new CodeLine(nr, opcode).withRef(ref).withMethodName(methodNameRef).withInput(inputSignature).withOutput(outputSignature);
|
||||
public static CodeLine line(int nr, Opcode opcode,
|
||||
Ref ref, String methodNameRef, String inputSignature) throws ClassNotFoundException {
|
||||
return new CodeLine(nr, opcode).withRef(ref).withMethodName(methodNameRef).withInput(parse(inputSignature)).withVoidOutput();
|
||||
}
|
||||
|
||||
public static CodeLine line(int nr, Opcode opcode,
|
||||
Ref ref, String methodNameRef, String inputSignature, String outputSignature) throws ClassNotFoundException {
|
||||
return new CodeLine(nr, opcode).withRef(ref).withMethodName(methodNameRef).withInput(parse(inputSignature)).withOutput(outputSignature);
|
||||
}
|
||||
|
||||
public static CodeLine line(int nr, Opcode opcode) {
|
||||
|
|
@ -63,7 +90,7 @@ public class CodeLine {
|
|||
}
|
||||
|
||||
private CodeLine withClassName(String className) {
|
||||
this.className = className;
|
||||
this.type = loadClass(className);
|
||||
return this;
|
||||
}
|
||||
|
||||
|
|
@ -72,7 +99,7 @@ public class CodeLine {
|
|||
return this;
|
||||
}
|
||||
|
||||
private CodeLine withInput(String inputSignature) {
|
||||
private CodeLine withInput(List<Class<?>> inputSignature) {
|
||||
this.inputSignature = inputSignature;
|
||||
return this;
|
||||
}
|
||||
|
|
@ -102,11 +129,9 @@ public class CodeLine {
|
|||
}
|
||||
|
||||
private CodeLine withExternalFieldRef(String className, String field) {
|
||||
this.className = className;
|
||||
this.externalFieldType = getType(className, field);
|
||||
this.externalfield = field;
|
||||
this.type = loadClass(className);
|
||||
this.externalfield = loadField(field);
|
||||
return this;
|
||||
|
||||
}
|
||||
|
||||
// TODO decide whether to work with Strings or class objects...
|
||||
|
|
@ -120,21 +145,21 @@ public class CodeLine {
|
|||
* @param field name of the field
|
||||
* @return the field type
|
||||
*/
|
||||
private String getType(String className, String field) {
|
||||
private Field loadField(String field) {
|
||||
try {
|
||||
return Class.forName(className).getField(field).getType().getName();
|
||||
} catch (ClassNotFoundException | NoSuchFieldException e) {
|
||||
return type.getField(field);
|
||||
} catch (NoSuchFieldException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
public boolean hasClassName() {
|
||||
return className != null;
|
||||
return type != null;
|
||||
}
|
||||
|
||||
|
||||
public String getClassName() {
|
||||
return className;
|
||||
return type.getName();
|
||||
}
|
||||
|
||||
public String getMethodName() {
|
||||
|
|
@ -146,7 +171,9 @@ public class CodeLine {
|
|||
}
|
||||
|
||||
public String getMethodSignature() {
|
||||
return inputSignature + outputSignature;
|
||||
return inputSignature.stream()
|
||||
.map(TypeMapper::map)
|
||||
.collect(Collectors.joining(",", "(", ")")) + outputSignature;
|
||||
}
|
||||
|
||||
public Ref getRef() {
|
||||
|
|
@ -183,11 +210,35 @@ public class CodeLine {
|
|||
return externalfield != null;
|
||||
}
|
||||
|
||||
public String getExternalfield() {
|
||||
public Field getExternalfield() {
|
||||
return externalfield;
|
||||
}
|
||||
|
||||
public String getExternalfieldClass() {
|
||||
return externalFieldType;
|
||||
public ConstantPoolEntry getAssignedEntry() {
|
||||
return assignedEntry;
|
||||
}
|
||||
|
||||
public void setAssignedEntry(ConstantPoolEntry assignedEntry) {
|
||||
this.assignedEntry = assignedEntry;
|
||||
}
|
||||
|
||||
public Opcode getOpcode() {
|
||||
return opcode;
|
||||
}
|
||||
|
||||
private Class<?> loadClass(String className) {
|
||||
try {
|
||||
return Class.forName(className);
|
||||
} catch (ClassNotFoundException e) {
|
||||
throw new RuntimeException(e); //TODO specific exception
|
||||
}
|
||||
}
|
||||
|
||||
public CodeContainer getOwner() {
|
||||
return owner;
|
||||
}
|
||||
|
||||
public void setOwner(CodeContainer codeContainer) {
|
||||
this.owner = codeContainer;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,11 +0,0 @@
|
|||
package nl.sander.beejava.classinfo;
|
||||
|
||||
import nl.sander.beejava.constantpool.entry.Utf8Entry;
|
||||
import nl.sander.beejava.util.ByteBuf;
|
||||
|
||||
public abstract class AttributeInfo {
|
||||
private Utf8Entry nameEntry;
|
||||
private int length;
|
||||
|
||||
public abstract void addBytes(ByteBuf buf);
|
||||
}
|
||||
|
|
@ -8,17 +8,13 @@ import nl.sander.beejava.util.ByteBuf;
|
|||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
public class FieldInfo {
|
||||
public class FieldInfo extends Info<FieldInfo> {
|
||||
private final Set<FieldAccessFlags> accessFlags = new HashSet<>();
|
||||
private final Utf8Entry nameEntry;
|
||||
private final Utf8Entry descriptorEntry;
|
||||
private final Set<AttributeInfo> attributes=new HashSet<>();
|
||||
|
||||
|
||||
public FieldInfo(Utf8Entry nameEntry, Utf8Entry descriptorEntry) {
|
||||
this.nameEntry = nameEntry;
|
||||
this.descriptorEntry = descriptorEntry;
|
||||
super(nameEntry, descriptorEntry);
|
||||
}
|
||||
|
||||
public FieldInfo addAccessFlags(Set<FieldAccessFlags> accessFlags) {
|
||||
this.accessFlags.addAll(accessFlags);
|
||||
return this;
|
||||
|
|
@ -28,18 +24,6 @@ public class FieldInfo {
|
|||
return accessFlags;
|
||||
}
|
||||
|
||||
public Utf8Entry getNameEntry() {
|
||||
return nameEntry;
|
||||
}
|
||||
|
||||
public Utf8Entry getDescriptorEntry() {
|
||||
return descriptorEntry;
|
||||
}
|
||||
|
||||
public Set<AttributeInfo> getAttributes() {
|
||||
return attributes;
|
||||
}
|
||||
|
||||
public void addBytes(ByteBuf buf) {
|
||||
buf.addU16(AccessFlags.combine(accessFlags));
|
||||
buf.addU16(nameEntry.getIndex());
|
||||
|
|
|
|||
36
src/main/java/nl/sander/beejava/classinfo/Info.java
Normal file
36
src/main/java/nl/sander/beejava/classinfo/Info.java
Normal file
|
|
@ -0,0 +1,36 @@
|
|||
package nl.sander.beejava.classinfo;
|
||||
|
||||
import nl.sander.beejava.classinfo.attributes.Attribute;
|
||||
import nl.sander.beejava.constantpool.entry.Utf8Entry;
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
public abstract class Info<T extends Info> {
|
||||
|
||||
protected final Utf8Entry nameEntry;
|
||||
protected final Utf8Entry descriptorEntry;
|
||||
protected final Set<Attribute> attributes = new HashSet<>();
|
||||
|
||||
public Info(Utf8Entry nameEntry, Utf8Entry descriptorEntry) {
|
||||
this.nameEntry = nameEntry;
|
||||
this.descriptorEntry = descriptorEntry;
|
||||
}
|
||||
|
||||
public Utf8Entry getNameEntry() {
|
||||
return nameEntry;
|
||||
}
|
||||
|
||||
public Utf8Entry getDescriptorEntry() {
|
||||
return descriptorEntry;
|
||||
}
|
||||
|
||||
public Set<Attribute> getAttributes() {
|
||||
return attributes;
|
||||
}
|
||||
|
||||
public T addAttribute(Attribute attribute) {
|
||||
attributes.add(attribute);
|
||||
return (T) this;
|
||||
}
|
||||
}
|
||||
28
src/main/java/nl/sander/beejava/classinfo/MethodInfo.java
Normal file
28
src/main/java/nl/sander/beejava/classinfo/MethodInfo.java
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
package nl.sander.beejava.classinfo;
|
||||
|
||||
import nl.sander.beejava.constantpool.entry.Utf8Entry;
|
||||
import nl.sander.beejava.flags.AccessFlags;
|
||||
import nl.sander.beejava.flags.MethodAccessFlags;
|
||||
import nl.sander.beejava.util.ByteBuf;
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
public class MethodInfo extends Info<MethodInfo> {
|
||||
private final Set<MethodAccessFlags> accessFlags = new HashSet<>();
|
||||
|
||||
public MethodInfo(Utf8Entry nameEntry, Utf8Entry descriptorEntry) {
|
||||
super(nameEntry, descriptorEntry);
|
||||
}
|
||||
public MethodInfo addAccessFlags(Set<MethodAccessFlags> accessFlags) {
|
||||
this.accessFlags.addAll(accessFlags);
|
||||
return this;
|
||||
}
|
||||
public void addBytes(ByteBuf buf) {
|
||||
buf.addU16(AccessFlags.combine(accessFlags));
|
||||
buf.addU16(nameEntry.getIndex());
|
||||
buf.addU16(descriptorEntry.getIndex());
|
||||
buf.addU16(attributes.size());
|
||||
attributes.forEach(ai -> ai.addBytes(buf));
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,15 @@
|
|||
package nl.sander.beejava.classinfo.attributes;
|
||||
|
||||
import nl.sander.beejava.constantpool.entry.Utf8Entry;
|
||||
import nl.sander.beejava.util.ByteBuf;
|
||||
|
||||
public abstract class Attribute {
|
||||
protected final Utf8Entry nameEntry;
|
||||
protected int length;
|
||||
|
||||
protected Attribute(Utf8Entry nameEntry) {
|
||||
this.nameEntry = nameEntry;
|
||||
}
|
||||
|
||||
public abstract void addBytes(ByteBuf buf);
|
||||
}
|
||||
|
|
@ -0,0 +1,64 @@
|
|||
package nl.sander.beejava.classinfo.attributes;
|
||||
|
||||
import nl.sander.beejava.constantpool.entry.Utf8Entry;
|
||||
import nl.sander.beejava.util.ByteBuf;
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* §4.7.3 The Code Attribute
|
||||
*/
|
||||
public class CodeAttribute extends Attribute {
|
||||
|
||||
private static final int MAX_STACK_LEN = 2; // nr of bytes for max_stack
|
||||
private static final int MAX_LOCALS_LEN = 2; // nr of bytes for max_locals
|
||||
private static final int CODE_LEN = 4; // nr of bytes for code_length
|
||||
private static final int NUM_EXCEPTION_HANDLERS_LEN = 2; // nr of bytes for number of exception_handlers
|
||||
private static final int NUM_ATTRIBUTES_LEN = 2; // nr of bytes for number of method attributes
|
||||
private static final int EXCEPTION_HANDLER_SIZE = 8; // nr of bytes per exception_hander
|
||||
|
||||
private final Set<Attribute> attributes = new HashSet<>();
|
||||
private final Set<ExceptionHandler> exceptionHandlers = new HashSet<>();
|
||||
private int maxStack; // u2
|
||||
private int maxLocals; // u2
|
||||
private byte[] code;
|
||||
|
||||
public CodeAttribute(Utf8Entry nameEntry) {
|
||||
super(nameEntry);
|
||||
}
|
||||
|
||||
public byte[] getCode() {
|
||||
return code;
|
||||
}
|
||||
|
||||
public void setCode(byte[] bytes) {
|
||||
this.code = bytes;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addBytes(ByteBuf buf) {
|
||||
buf.addU16(nameEntry.getIndex());
|
||||
buf.addU32(MAX_STACK_LEN +
|
||||
MAX_LOCALS_LEN +
|
||||
CODE_LEN +
|
||||
code.length +
|
||||
NUM_EXCEPTION_HANDLERS_LEN +
|
||||
exceptionHandlers.size() * EXCEPTION_HANDLER_SIZE +
|
||||
NUM_ATTRIBUTES_LEN); //TODO works only if attributes are empty
|
||||
buf.addU16(maxStack);
|
||||
buf.addU16(maxLocals);
|
||||
buf.addU32(code.length);
|
||||
buf.addU8(getCode());
|
||||
buf.addU16(exceptionHandlers.size());
|
||||
buf.addU16(attributes.size());
|
||||
}
|
||||
|
||||
public void setMaxStack(int maxStack) {
|
||||
this.maxStack = maxStack;
|
||||
}
|
||||
|
||||
public void setMaxLocals(int maxLocals) {
|
||||
this.maxLocals = maxLocals;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
package nl.sander.beejava.classinfo.attributes;
|
||||
|
||||
/**
|
||||
* see §4.7.3 The code attribute
|
||||
*/
|
||||
public class ExceptionHandler {
|
||||
private int startPc; //u2
|
||||
private int endPc; //u2
|
||||
private int handlerPc; //u2
|
||||
private int catchType; //u2
|
||||
}
|
||||
|
|
@ -64,4 +64,5 @@ public abstract class ConstantPoolEntry {
|
|||
protected byte getByte(long bits, int positions) {
|
||||
return (byte) ((bits >>> (positions * 8)) & 0xFF);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -31,7 +31,7 @@ public class FieldRefEntry extends ConstantPoolEntry {
|
|||
|
||||
@Override
|
||||
public byte[] getBytes() {
|
||||
return new byte[]{TAG};
|
||||
return new byte[]{TAG, upperByte(getClassIndex()), lowerByte(getClassIndex()), upperByte(getNameAndTypeIndex()),lowerByte(getNameAndTypeIndex())};
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
|||
|
|
@ -40,6 +40,10 @@ public class ByteBuf {
|
|||
addU8((u16 >>> 8) & 0xFF, u16 & 0xFF);
|
||||
}
|
||||
|
||||
public void addU32(int u32) {
|
||||
addU8((u32 >>> 24) & 0xFF,(u32 >>> 16) & 0xFF, (u32 >>> 8) & 0xFF, u32 & 0xFF);
|
||||
}
|
||||
|
||||
private void enlarge(final int size) {
|
||||
final int length1 = 2 * data.limit();
|
||||
final int length2 = data.limit() + size;
|
||||
|
|
@ -56,4 +60,26 @@ public class ByteBuf {
|
|||
data.get(arr);
|
||||
return arr;
|
||||
}
|
||||
|
||||
public String toString() {
|
||||
return toString(StandardCharsets.UTF_8);
|
||||
}
|
||||
|
||||
public String toString(Charset charset) {
|
||||
data.flip();
|
||||
|
||||
CharsetDecoder decoder = charset.newDecoder(); // decode is not threadsafe, might put it in threadlocal
|
||||
// but I don't think this (newDecoder()+config) is expensive
|
||||
|
||||
decoder.onMalformedInput(CodingErrorAction.REPLACE)
|
||||
.onUnmappableCharacter(CodingErrorAction.REPLACE);
|
||||
|
||||
try {
|
||||
return decoder.decode(data).toString();
|
||||
} catch (CharacterCodingException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,8 +8,8 @@ import java.io.IOException;
|
|||
|
||||
public class BytecodeGeneratorTests {
|
||||
@Test
|
||||
public void testEmpty() throws IOException {
|
||||
byte[] bytecode = BytecodeGenerator.generate(Compiler.compile(TestData.emptyClass()));
|
||||
public void testEmpty() throws IOException, ClassNotFoundException {
|
||||
byte[] bytecode = BytecodeGenerator.generate(Compiler.compile(TestData.createEmptyClass()));
|
||||
File dir = new File("target/nl/sander/beejava/test");
|
||||
dir.mkdirs();
|
||||
try (FileOutputStream outputStream = new FileOutputStream(new File(dir, "EmptyBean.class"))) {
|
||||
|
|
@ -18,12 +18,12 @@ public class BytecodeGeneratorTests {
|
|||
}
|
||||
|
||||
@Test
|
||||
public void testInterface() {
|
||||
public void testInterface() throws ClassNotFoundException {
|
||||
BytecodeGenerator.generate(Compiler.compile(TestData.emptyClassWithInterface()));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testFields() {
|
||||
public void testFields() throws ClassNotFoundException {
|
||||
BytecodeGenerator.generate(Compiler.compile(TestData.createClassWithField(int.class)));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -14,16 +14,16 @@ public class CompilerTests {
|
|||
|
||||
// creates simplest class possible and checks the tree, that the ConstantTreeCreator emits
|
||||
@Test // This is not a maintainable test
|
||||
public void testMethodRefEntryForSuperConstructor() {
|
||||
public void testMethodRefEntryForSuperConstructor() throws ClassNotFoundException {
|
||||
// Arrange
|
||||
BeeSource classWithIntField = TestData.emptyClass();
|
||||
BeeSource classWithIntField = TestData.createEmptyClass();
|
||||
|
||||
// Act
|
||||
CompiledClass compiledClass = Compiler.compile(classWithIntField);
|
||||
|
||||
// Assert
|
||||
Set<ConstantPoolEntry> constantTree = compiledClass.getConstantTree();
|
||||
assertEquals(2, constantTree.size());
|
||||
assertEquals(3, constantTree.size());
|
||||
ConstantPoolEntry superConstructor = constantTree.iterator().next();
|
||||
|
||||
assertEquals(MethodRefEntry.class, superConstructor.getClass());
|
||||
|
|
|
|||
|
|
@ -3,8 +3,11 @@ package nl.sander.beejava;
|
|||
import nl.sander.beejava.api.BeeSource;
|
||||
import nl.sander.beejava.constantpool.ConstantPool;
|
||||
import nl.sander.beejava.constantpool.entry.ClassEntry;
|
||||
import nl.sander.beejava.constantpool.entry.ConstantPoolEntry;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileOutputStream;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
|
|
@ -12,9 +15,9 @@ import static org.junit.jupiter.api.Assertions.assertEquals;
|
|||
|
||||
public class ConstantPoolUniquenessTests {
|
||||
@Test
|
||||
public void test() {
|
||||
public void test() throws Exception {
|
||||
// Arrange
|
||||
BeeSource someClass = TestData.createClassWithTwoReferencesToSomeClass();
|
||||
BeeSource someClass = TestData.createEmptyClass();
|
||||
|
||||
// Act
|
||||
CompiledClass compiledClass = Compiler.compile(someClass);
|
||||
|
|
@ -27,6 +30,48 @@ public class ConstantPoolUniquenessTests {
|
|||
.filter(ce -> ce.getName().equals("java/lang/System"))
|
||||
.collect(Collectors.toList());
|
||||
|
||||
assertEquals(1, refsToSystem.size());
|
||||
assertEquals(0, refsToSystem.size());
|
||||
|
||||
|
||||
byte[] bytecode = BytecodeGenerator.generate(compiledClass);
|
||||
|
||||
int x = 1;
|
||||
for (ConstantPoolEntry e : constantPool) {
|
||||
System.out.println((x++) + ":" + e);
|
||||
}
|
||||
|
||||
x = 1;
|
||||
for (ConstantPoolEntry e : constantPool) {
|
||||
System.out.print((x++) + ":");
|
||||
printBytes(e.getBytes());
|
||||
}
|
||||
|
||||
|
||||
File dir = new File("target/nl/sander/beejava/test");
|
||||
dir.mkdirs();
|
||||
try (FileOutputStream outputStream = new FileOutputStream(new File(dir, "EmptyBean.class"))) {
|
||||
outputStream.write(bytecode);
|
||||
}
|
||||
printBytes2(bytecode);
|
||||
}
|
||||
|
||||
//TODO remove
|
||||
private void printBytes(byte[] bytes) {
|
||||
for (byte b : bytes) {
|
||||
System.out.print(String.format("%2s", Integer.toHexString(b & 0xFF)).replace(' ', '0') + " ");
|
||||
}
|
||||
System.out.println();
|
||||
}
|
||||
|
||||
private void printBytes2(byte[] bytes) {
|
||||
int count = 0;
|
||||
for (byte b : bytes) {
|
||||
System.out.print(String.format("%2s", Integer.toHexString(b & 0xFF)).replace(' ', '0') + (count % 2 == 0 ? "" : " "));
|
||||
count += 1;
|
||||
if (count > 15) {
|
||||
count = 0;
|
||||
System.out.println();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,20 +9,21 @@ import java.io.Serializable;
|
|||
import static nl.sander.beejava.api.CodeLine.line;
|
||||
import static nl.sander.beejava.api.Opcode.*;
|
||||
import static nl.sander.beejava.flags.ClassAccessFlags.PUBLIC;
|
||||
import static nl.sander.beejava.flags.ClassAccessFlags.SUPER;
|
||||
|
||||
public class TestData {
|
||||
public static BeeSource emptyClass() {
|
||||
public static BeeSource createEmptyClass() throws ClassNotFoundException {
|
||||
return BeeSource.builder()
|
||||
.withClassFileVersion(Version.V14)
|
||||
.withPackage("nl.sander.beejava.test")
|
||||
.withAccessFlags(PUBLIC)
|
||||
.withAccessFlags(PUBLIC, SUPER)
|
||||
.withSimpleName("EmptyBean")
|
||||
.withSuperClass(Object.class) // Not mandatory, like in java sourcecode
|
||||
.withConstructors(createConstructor()) // There's no default constructor in beejava. The user must always add them
|
||||
.build();
|
||||
}
|
||||
|
||||
public static BeeSource emptyClassWithInterface() {
|
||||
public static BeeSource emptyClassWithInterface() throws ClassNotFoundException {
|
||||
return BeeSource.builder()
|
||||
.withClassFileVersion(Version.V14)
|
||||
.withPackage("nl.sander.beejava.test")
|
||||
|
|
@ -34,36 +35,41 @@ public class TestData {
|
|||
.build();
|
||||
}
|
||||
|
||||
public static BeeSource createClassWithTwoReferencesToSomeClass() {
|
||||
public static BeeSource createClassWithTwoReferencesToSomeClass() throws ClassNotFoundException {
|
||||
BeeMethod print1 = BeeMethod.builder()
|
||||
.withName("print1")
|
||||
.withAccessFlags(MethodAccessFlags.PUBLIC)
|
||||
.withCode(
|
||||
line(0, GET, "java.lang.System","out"),
|
||||
line(1, LD_CONST, "1"),
|
||||
line(2, INVOKE, "java.io.PrintStream", "println", "(java.lang.String)"),
|
||||
line(2, INVOKE, "java.io.PrintStream", "println", "java.lang.String"),
|
||||
line(3, RETURN))
|
||||
.build();
|
||||
|
||||
// INVOKE System.out.println("1")
|
||||
|
||||
|
||||
BeeMethod print2 = BeeMethod.builder()
|
||||
.withName("print2")
|
||||
.withAccessFlags(MethodAccessFlags.PUBLIC)
|
||||
.withCode(
|
||||
line(0, GET, "java.lang.System","out"),
|
||||
line(1, LD_CONST, "2"),
|
||||
line(2, INVOKE, "java.io.PrintStream", "println", "(java.lang.String)"),
|
||||
line(2, INVOKE, "java.io.PrintStream", "println", "java.lang.String"),
|
||||
line(3, RETURN))
|
||||
.build();
|
||||
|
||||
return BeeSource.builder()
|
||||
.withClassFileVersion(Version.V14)
|
||||
.withPackage("nl.sander.beejava.test")
|
||||
.withAccessFlags(PUBLIC)
|
||||
.withAccessFlags(PUBLIC, SUPER)
|
||||
.withSimpleName("ClassWithReferences")
|
||||
.withConstructors(createConstructor()) // There's no default constructor in beejava. The user must always add them
|
||||
.withMethods(print1, print2)
|
||||
.build();
|
||||
}
|
||||
|
||||
public static BeeSource createClassWithField(Class<?> fieldType) {
|
||||
public static BeeSource createClassWithField(Class<?> fieldType) throws ClassNotFoundException {
|
||||
BeeField field = BeeField.builder()
|
||||
.withAccessFlags(FieldAccessFlags.PRIVATE)
|
||||
.withType(fieldType)
|
||||
|
|
@ -95,7 +101,7 @@ public class TestData {
|
|||
.build();
|
||||
}
|
||||
|
||||
private static BeeConstructor createConstructor() {
|
||||
private static BeeConstructor createConstructor() throws ClassNotFoundException {
|
||||
return BeeConstructor.builder()
|
||||
.withAccessFlags(MethodAccessFlags.PUBLIC)
|
||||
.withCode(
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ import static org.junit.jupiter.api.Assertions.fail;
|
|||
public class TypeMapperTest {
|
||||
|
||||
@Test
|
||||
public void test_int() {
|
||||
public void test_int() throws ClassNotFoundException {
|
||||
// Arrange
|
||||
BeeSource beeSource = TestData.createClassWithField(int.class);
|
||||
|
||||
|
|
@ -24,7 +24,7 @@ public class TypeMapperTest {
|
|||
}
|
||||
|
||||
@Test
|
||||
public void test_double() {
|
||||
public void test_double() throws ClassNotFoundException {
|
||||
// Arrange
|
||||
BeeSource beeSource = TestData.createClassWithField(double.class);
|
||||
|
||||
|
|
@ -37,7 +37,7 @@ public class TypeMapperTest {
|
|||
}
|
||||
|
||||
@Test
|
||||
public void test_float() {
|
||||
public void test_float() throws ClassNotFoundException {
|
||||
// Arrange
|
||||
BeeSource beeSource = TestData.createClassWithField(float.class);
|
||||
|
||||
|
|
@ -50,7 +50,7 @@ public class TypeMapperTest {
|
|||
}
|
||||
|
||||
@Test
|
||||
public void test_byte() {
|
||||
public void test_byte() throws ClassNotFoundException {
|
||||
// Arrange
|
||||
BeeSource beeSource = TestData.createClassWithField(byte.class);
|
||||
|
||||
|
|
@ -63,7 +63,7 @@ public class TypeMapperTest {
|
|||
}
|
||||
|
||||
@Test
|
||||
public void test_short() {
|
||||
public void test_short() throws ClassNotFoundException {
|
||||
// Arrange
|
||||
BeeSource beeSource = TestData.createClassWithField(short.class);
|
||||
|
||||
|
|
@ -76,7 +76,7 @@ public class TypeMapperTest {
|
|||
}
|
||||
|
||||
@Test
|
||||
public void test_long() {
|
||||
public void test_long() throws ClassNotFoundException {
|
||||
// Arrange
|
||||
BeeSource beeSource = TestData.createClassWithField(long.class);
|
||||
|
||||
|
|
@ -89,7 +89,7 @@ public class TypeMapperTest {
|
|||
}
|
||||
|
||||
@Test
|
||||
public void test_char() {
|
||||
public void test_char() throws ClassNotFoundException {
|
||||
// Arrange
|
||||
BeeSource beeSource = TestData.createClassWithField(char.class);
|
||||
|
||||
|
|
@ -102,7 +102,7 @@ public class TypeMapperTest {
|
|||
}
|
||||
|
||||
@Test
|
||||
public void test_boolean() {
|
||||
public void test_boolean() throws ClassNotFoundException {
|
||||
// Arrange
|
||||
BeeSource beeSource = TestData.createClassWithField(boolean.class);
|
||||
|
||||
|
|
@ -115,7 +115,7 @@ public class TypeMapperTest {
|
|||
}
|
||||
|
||||
@Test
|
||||
public void test_Object() {
|
||||
public void test_Object() throws ClassNotFoundException {
|
||||
// Arrange
|
||||
BeeSource beeSource = TestData.createClassWithField(Object.class);
|
||||
|
||||
|
|
@ -124,11 +124,11 @@ public class TypeMapperTest {
|
|||
|
||||
// Assert
|
||||
NameAndTypeEntry fieldEntry = getFieldNameAndType(constantPool);
|
||||
assertEquals("Ljava/lang/Object", fieldEntry.getType());
|
||||
assertEquals("Ljava/lang/Object;", fieldEntry.getType());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void test_int_array() {
|
||||
public void test_int_array() throws ClassNotFoundException {
|
||||
// Arrange
|
||||
BeeSource beeSource = TestData.createClassWithField(int[].class);
|
||||
|
||||
|
|
@ -141,7 +141,7 @@ public class TypeMapperTest {
|
|||
}
|
||||
|
||||
@Test
|
||||
public void test_Object_array() {
|
||||
public void test_Object_array() throws ClassNotFoundException {
|
||||
// Arrange
|
||||
BeeSource beeSource = TestData.createClassWithField(String[].class);
|
||||
|
||||
|
|
|
|||
21
src/test/java/nl/sander/beejava/e2e/ByteClassLoader.java
Normal file
21
src/test/java/nl/sander/beejava/e2e/ByteClassLoader.java
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
package nl.sander.beejava.e2e;
|
||||
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.ConcurrentMap;
|
||||
|
||||
public class ByteClassLoader extends ClassLoader {
|
||||
|
||||
private final ConcurrentMap<String, byte[]> classByteCode = new ConcurrentHashMap<>();
|
||||
|
||||
@Override
|
||||
protected Class<?> findClass(String name) throws ClassNotFoundException {
|
||||
byte[] bytecode = Optional.ofNullable(classByteCode.get(name)).orElseThrow(() -> new ClassNotFoundException(name));
|
||||
return defineClass(name, bytecode, 0, bytecode.length);
|
||||
}
|
||||
|
||||
|
||||
public void setByteCode(String className, byte[] bytecode) {
|
||||
classByteCode.putIfAbsent(className, bytecode);
|
||||
}
|
||||
}
|
||||
40
src/test/java/nl/sander/beejava/e2e/EmptyBeanTest.java
Normal file
40
src/test/java/nl/sander/beejava/e2e/EmptyBeanTest.java
Normal file
|
|
@ -0,0 +1,40 @@
|
|||
package nl.sander.beejava.e2e;
|
||||
|
||||
import nl.sander.beejava.BytecodeGenerator;
|
||||
import nl.sander.beejava.CompiledClass;
|
||||
import nl.sander.beejava.Compiler;
|
||||
import nl.sander.beejava.TestData;
|
||||
import nl.sander.beejava.api.BeeSource;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.lang.reflect.Constructor;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||
|
||||
/**
|
||||
* A rather simple case, still meaningful nonetheless.
|
||||
* Green means class can be loaded, so the class file structure is valid.
|
||||
* The EmptyBean class just contains a default constructor.
|
||||
*/
|
||||
public class EmptyBeanTest {
|
||||
@Test
|
||||
public void testEmptyBean() throws Exception {
|
||||
// Arrange
|
||||
BeeSource emptyClass = TestData.createEmptyClass();
|
||||
|
||||
// Act
|
||||
CompiledClass compiledClass = Compiler.compile(emptyClass);
|
||||
byte[] bytecode = BytecodeGenerator.generate(compiledClass);
|
||||
|
||||
ByteClassLoader classLoader = new ByteClassLoader();
|
||||
classLoader.setByteCode("nl.sander.beejava.test.EmptyBean", bytecode);
|
||||
|
||||
// Assert
|
||||
Constructor<?> constructor = classLoader.loadClass("nl.sander.beejava.test.EmptyBean").getConstructor();
|
||||
assertNotNull(constructor);
|
||||
|
||||
Object instance = constructor.newInstance();
|
||||
assertNotNull(instance);
|
||||
|
||||
}
|
||||
}
|
||||
|
|
@ -8,12 +8,7 @@ public class BeanWithClassReferences {
|
|||
}
|
||||
|
||||
public void print2() {
|
||||
System.out.println(create2());
|
||||
System.out.println("2");
|
||||
}
|
||||
|
||||
public String create2() {
|
||||
StringBuilder stringBuilder = new StringBuilder();
|
||||
stringBuilder.append("2");
|
||||
return stringBuilder.toString();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
7
src/test/java/nl/sander/beejava/testclasses/Dummy.java
Normal file
7
src/test/java/nl/sander/beejava/testclasses/Dummy.java
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
package nl.sander.beejava.testclasses;
|
||||
|
||||
public class Dummy {
|
||||
public String hello(String goodbye){
|
||||
return "hello";
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Reference in a new issue