added better support for methods, maxLocals and maxStack is now calculated (was dummy constant before). JavaOpcodes contains info about the number of pops/pushes and an indication of wide-ness (1 or 2 bytes for constantpool index)
This commit is contained in:
parent
282605ac7d
commit
7412b7ba43
13 changed files with 177 additions and 40 deletions
|
|
@ -54,4 +54,6 @@ public abstract class CodeContainer {
|
||||||
}
|
}
|
||||||
this.owner = beeSource;
|
this.owner = beeSource;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public abstract boolean isConstructor();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -40,7 +40,7 @@ public class CompiledClass {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Set<ConstantPoolEntry> getConstantTree() {
|
public Set<ConstantPoolEntry> getConstantTree() {
|
||||||
return constantTree;
|
return constantTree;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -68,8 +68,7 @@ public class Compiler {
|
||||||
this.codeAttributeNameEntry = constantPoolEntryCreator.getOrCreateUtf8("Code");
|
this.codeAttributeNameEntry = constantPoolEntryCreator.getOrCreateUtf8("Code");
|
||||||
compiledClass.addConstantPoolEntry(codeAttributeNameEntry);
|
compiledClass.addConstantPoolEntry(codeAttributeNameEntry);
|
||||||
|
|
||||||
ConstantPool constantPool = constantPoolCreator.createConstantPool(compiledClass.getConstantTree());
|
return constantPoolCreator.createConstantPool(compiledClass.getConstantTree());
|
||||||
return constantPool;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void addConstructors() {
|
public void addConstructors() {
|
||||||
|
|
@ -98,13 +97,15 @@ public class Compiler {
|
||||||
constantPoolEntryCreator.getOrCreateUtf8(method.getName()),
|
constantPoolEntryCreator.getOrCreateUtf8(method.getName()),
|
||||||
constantPoolEntryCreator.getOrCreateUtf8(method.getSignature()))
|
constantPoolEntryCreator.getOrCreateUtf8(method.getSignature()))
|
||||||
.addAccessFlags(method.getAccessFlags())
|
.addAccessFlags(method.getAccessFlags())
|
||||||
.addAttribute(MethodCodeCreator.createCodeAttribute(codeAttributeNameEntry, method.getCode()));
|
.addAttribute(MethodCodeCreator.createCodeAttribute(codeAttributeNameEntry, method));
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* inspect a method or constructor, extract items that need to be added, and add them to the constant pool
|
* inspect a method or constructor, extract items that need to be added, and add them to the constant pool
|
||||||
*/
|
*/
|
||||||
private void updateConstantPool(CodeContainer codeContainer) {
|
private void updateConstantPool(CodeContainer codeContainer) {
|
||||||
|
compiledClass.addConstantPoolEntry(constantPoolEntryCreator.getOrCreateUtf8(codeContainer.getName()));
|
||||||
|
compiledClass.addConstantPoolEntry(constantPoolEntryCreator.getOrCreateUtf8(codeContainer.getSignature()));
|
||||||
codeContainer.getCode().forEach(this::updateConstantPool);
|
codeContainer.getCode().forEach(this::updateConstantPool);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
40
src/main/java/nl/sander/beejava/JavaOpcode.java
Normal file
40
src/main/java/nl/sander/beejava/JavaOpcode.java
Normal file
|
|
@ -0,0 +1,40 @@
|
||||||
|
package nl.sander.beejava;
|
||||||
|
|
||||||
|
public enum JavaOpcode {
|
||||||
|
LDC(0x12, false, +1),
|
||||||
|
LDC_W(0x13,true, +1),
|
||||||
|
LDC2_W ( 0x14, true, +2),
|
||||||
|
|
||||||
|
ALOAD_0 ( 0x2a, false, +1),
|
||||||
|
RETURN ( 0xb1,false, 0),
|
||||||
|
GETSTATIC ( 0xb2,true, +1),
|
||||||
|
GETFIELD ( 0xb4,true, +1),
|
||||||
|
|
||||||
|
INVOKEINTERFACE ( 0xb5, true, -1),
|
||||||
|
INVOKEVIRTUAL ( 0xb6, true, -1),
|
||||||
|
INVOKESPECIAL ( 0xb7, true, -1),
|
||||||
|
INVOKESTATIC ( 0xb8, true, -1),
|
||||||
|
INVOKEDYNAMIC ( 0xba,true,-1);
|
||||||
|
|
||||||
|
private final int opcode;
|
||||||
|
private final boolean wide;
|
||||||
|
private final int stackDif;
|
||||||
|
|
||||||
|
JavaOpcode(int opcode, boolean wide, int stackDif) {
|
||||||
|
this.opcode=opcode;
|
||||||
|
this.wide=wide;
|
||||||
|
this.stackDif=stackDif;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getByteCode() {
|
||||||
|
return opcode;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isWide() {
|
||||||
|
return wide;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getStackDif() {
|
||||||
|
return stackDif;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,19 +0,0 @@
|
||||||
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;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
@ -3,26 +3,64 @@ package nl.sander.beejava;
|
||||||
import nl.sander.beejava.api.CodeLine;
|
import nl.sander.beejava.api.CodeLine;
|
||||||
import nl.sander.beejava.classinfo.attributes.CodeAttribute;
|
import nl.sander.beejava.classinfo.attributes.CodeAttribute;
|
||||||
import nl.sander.beejava.constantpool.entry.ConstantPoolEntry;
|
import nl.sander.beejava.constantpool.entry.ConstantPoolEntry;
|
||||||
|
import nl.sander.beejava.constantpool.entry.MethodRefEntry;
|
||||||
import nl.sander.beejava.constantpool.entry.Utf8Entry;
|
import nl.sander.beejava.constantpool.entry.Utf8Entry;
|
||||||
import nl.sander.beejava.util.ByteBuf;
|
import nl.sander.beejava.util.ByteBuf;
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
public class MethodCodeCreator {
|
public class MethodCodeCreator {
|
||||||
|
|
||||||
public static CodeAttribute createCodeAttribute(Utf8Entry codeAttributeNameEntry, List<CodeLine> code) {
|
public static CodeAttribute createCodeAttribute(Utf8Entry codeAttributeNameEntry, CodeContainer codeContainer) {
|
||||||
CodeAttribute codeAttribute = new CodeAttribute(codeAttributeNameEntry);
|
CodeAttribute codeAttribute = new CodeAttribute(codeAttributeNameEntry);
|
||||||
codeAttribute.setMaxStack(1);
|
codeAttribute.setMaxStack(calculateMaxStack(codeContainer));
|
||||||
codeAttribute.setMaxLocals(1);
|
codeAttribute.setMaxLocals(codeContainer.formalParameters.size() + 1);
|
||||||
ByteBuf byteBuf = new ByteBuf();
|
ByteBuf byteBuf = new ByteBuf();
|
||||||
code.forEach(codeLine -> {
|
|
||||||
|
codeContainer.getCode().forEach(codeLine -> {
|
||||||
|
JavaOpcode javaOpcode = codeLine.getJavaOpcode(); // the opcode we determined in calculateMaxStack
|
||||||
|
byteBuf.addU8(javaOpcode.getByteCode());
|
||||||
ConstantPoolEntry constantPoolEntry = codeLine.getAssignedEntry();
|
ConstantPoolEntry constantPoolEntry = codeLine.getAssignedEntry();
|
||||||
byteBuf.addU8(OpcodeMapper.mapOpcode(codeLine));
|
|
||||||
if (constantPoolEntry != null) {
|
if (constantPoolEntry != null) {
|
||||||
byteBuf.addU16(constantPoolEntry.getIndex());
|
if (javaOpcode.isWide()) {
|
||||||
|
byteBuf.addU16(constantPoolEntry.getIndex());
|
||||||
|
} else if (constantPoolEntry.getIndex() < 0x100) {
|
||||||
|
byteBuf.addU8(constantPoolEntry.getIndex());
|
||||||
|
} else {
|
||||||
|
throw new IllegalStateException("cannot use non-wide opcode with wide constantpool index");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
codeAttribute.setCode(byteBuf.toBytes());
|
codeAttribute.setCode(byteBuf.toBytes());
|
||||||
return codeAttribute;
|
return codeAttribute;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static int calculateMaxStack(CodeContainer codeContainer) {
|
||||||
|
int stackSize = 0;
|
||||||
|
int maxStackSize = 0;
|
||||||
|
for (CodeLine codeLine : codeContainer.getCode()) {
|
||||||
|
JavaOpcode javaOpcode = OpcodeMapper.mapOpcode(codeLine);
|
||||||
|
codeLine.setJavaOpcode(javaOpcode); //not really nice that we mutate codeLine, but this way we don't have to calculate the JavaOpcode twice
|
||||||
|
stackSize += javaOpcode.getStackDif();
|
||||||
|
ConstantPoolEntry assignedEntry = codeLine.getAssignedEntry();
|
||||||
|
if (assignedEntry instanceof MethodRefEntry) {
|
||||||
|
MethodRefEntry methodRefEntry = (MethodRefEntry) assignedEntry;
|
||||||
|
String argumentTypes = methodRefEntry.getNameAndType().getType();
|
||||||
|
stackSize -= getNrArguments(argumentTypes);
|
||||||
|
}
|
||||||
|
if (stackSize > maxStackSize) {
|
||||||
|
maxStackSize = stackSize;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return maxStackSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static int getNrArguments(String argumentTypes) {
|
||||||
|
int nr = 0;
|
||||||
|
for (int i = 0; i < argumentTypes.length(); i++) {
|
||||||
|
if (argumentTypes.charAt(i) == ',') {
|
||||||
|
nr += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nr;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -8,33 +8,33 @@ import nl.sander.beejava.flags.ClassAccessFlags;
|
||||||
import java.lang.reflect.Field;
|
import java.lang.reflect.Field;
|
||||||
import java.lang.reflect.Modifier;
|
import java.lang.reflect.Modifier;
|
||||||
|
|
||||||
import static nl.sander.beejava.JavaOpcodes.*;
|
import static nl.sander.beejava.JavaOpcode.*;
|
||||||
|
|
||||||
public class OpcodeMapper {
|
public class OpcodeMapper {
|
||||||
|
|
||||||
public static int mapOpcode(CodeLine codeLine) {
|
public static JavaOpcode mapOpcode(CodeLine codeLine) {
|
||||||
Opcode opcode = codeLine.getOpcode();
|
Opcode opcode = codeLine.getOpcode();
|
||||||
return switch (opcode) {
|
return switch (opcode) {
|
||||||
case GET -> isStatic(codeLine.getExternalfield()) ? GETSTATIC : GETFIELD;
|
case GET -> isStatic(codeLine.getExternalfield()) ? GETSTATIC : GETFIELD;
|
||||||
case LD_VAR -> ALOAD_0;
|
case LD_VAR -> ALOAD_0;
|
||||||
case LD_CONST -> loadConst(codeLine);
|
case LD_CONST -> loadConst(codeLine);
|
||||||
case INVOKE -> invoke(codeLine);
|
case INVOKE -> invoke(codeLine);
|
||||||
case RETURN -> JavaOpcodes.RETURN; //TODO not complete yet
|
case RETURN -> JavaOpcode.RETURN; //TODO not complete yet
|
||||||
default -> 0;
|
default -> throw new IllegalStateException("something not implemented");
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
/* TODO cover more cases */
|
/* TODO cover more cases */
|
||||||
private static int invoke(CodeLine codeLine) {
|
private static JavaOpcode invoke(CodeLine codeLine) {
|
||||||
BeeSource source = codeLine.getOwner().getOwner();
|
BeeSource source = codeLine.getOwner().getOwner();
|
||||||
if (source.getAccessFlags().contains(ClassAccessFlags.SUPER)) {
|
if (source.getAccessFlags().contains(ClassAccessFlags.SUPER) && codeLine.getOwner().isConstructor()) {
|
||||||
return INVOKESPECIAL;
|
return INVOKESPECIAL;
|
||||||
} else {
|
} else {
|
||||||
return INVOKEVIRTUAL;
|
return INVOKEVIRTUAL;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static int loadConst(CodeLine codeLine) {
|
private static JavaOpcode loadConst(CodeLine codeLine) {
|
||||||
Object constValue = codeLine.getConstValue();
|
Object constValue = codeLine.getConstValue();
|
||||||
int index = codeLine.getAssignedEntry().getIndex();
|
int index = codeLine.getAssignedEntry().getIndex();
|
||||||
if (constValue instanceof Double || constValue instanceof Long) {
|
if (constValue instanceof Double || constValue instanceof Long) {
|
||||||
|
|
|
||||||
5
src/main/java/nl/sander/beejava/StackOp.java
Normal file
5
src/main/java/nl/sander/beejava/StackOp.java
Normal file
|
|
@ -0,0 +1,5 @@
|
||||||
|
package nl.sander.beejava;
|
||||||
|
|
||||||
|
public enum StackOp {
|
||||||
|
PUSH,POP
|
||||||
|
}
|
||||||
|
|
@ -38,6 +38,11 @@ public final class BeeConstructor extends CodeContainer {
|
||||||
return Void.class;
|
return Void.class;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isConstructor() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean equals(Object o) {
|
public boolean equals(Object o) {
|
||||||
if (this == o) return true;
|
if (this == o) return true;
|
||||||
|
|
|
||||||
|
|
@ -37,6 +37,11 @@ public final class BeeMethod extends CodeContainer {
|
||||||
return returnType;
|
return returnType;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isConstructor() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
public void validate() {
|
public void validate() {
|
||||||
//TODO
|
//TODO
|
||||||
/*
|
/*
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@ package nl.sander.beejava.api;
|
||||||
|
|
||||||
|
|
||||||
import nl.sander.beejava.CodeContainer;
|
import nl.sander.beejava.CodeContainer;
|
||||||
|
import nl.sander.beejava.JavaOpcode;
|
||||||
import nl.sander.beejava.TypeMapper;
|
import nl.sander.beejava.TypeMapper;
|
||||||
import nl.sander.beejava.constantpool.entry.ConstantPoolEntry;
|
import nl.sander.beejava.constantpool.entry.ConstantPoolEntry;
|
||||||
|
|
||||||
|
|
@ -22,11 +23,10 @@ public final class CodeLine {
|
||||||
private String outputSignature;
|
private String outputSignature;
|
||||||
private BeeField ownfield; // when you create a class with a field, you can refer to it
|
private BeeField ownfield; // when you create a class with a field, you can refer to it
|
||||||
private Field 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 Object constValue;
|
||||||
|
|
||||||
private ConstantPoolEntry assignedEntry;
|
private ConstantPoolEntry assignedEntry;
|
||||||
private CodeContainer owner;
|
private CodeContainer owner;
|
||||||
|
private JavaOpcode javaOpcode;
|
||||||
|
|
||||||
CodeLine(int linenumber, Opcode opcode) {
|
CodeLine(int linenumber, Opcode opcode) {
|
||||||
this.linenumber = linenumber;
|
this.linenumber = linenumber;
|
||||||
|
|
@ -241,4 +241,12 @@ public final class CodeLine {
|
||||||
public void setOwner(CodeContainer codeContainer) {
|
public void setOwner(CodeContainer codeContainer) {
|
||||||
this.owner = codeContainer;
|
this.owner = codeContainer;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public JavaOpcode getJavaOpcode() {
|
||||||
|
return javaOpcode;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setJavaOpcode(JavaOpcode javaOpcode) {
|
||||||
|
this.javaOpcode = javaOpcode;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,7 @@ public class MethodRefEntry extends ConstantPoolEntry {
|
||||||
private final ClassEntry classRef;
|
private final ClassEntry classRef;
|
||||||
private final NameAndTypeEntry nameAndType;
|
private final NameAndTypeEntry nameAndType;
|
||||||
|
|
||||||
|
|
||||||
public MethodRefEntry(ClassEntry classRef, NameAndTypeEntry nameAndType) {
|
public MethodRefEntry(ClassEntry classRef, NameAndTypeEntry nameAndType) {
|
||||||
super(classRef, nameAndType);
|
super(classRef, nameAndType);
|
||||||
this.classRef = classRef;
|
this.classRef = classRef;
|
||||||
|
|
@ -22,6 +23,9 @@ public class MethodRefEntry extends ConstantPoolEntry {
|
||||||
return nameAndType.getIndex();
|
return nameAndType.getIndex();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public NameAndTypeEntry getNameAndType() {
|
||||||
|
return nameAndType;
|
||||||
|
}
|
||||||
|
|
||||||
public byte[] getBytes() {
|
public byte[] getBytes() {
|
||||||
return new byte[]{TAG, upperByte(getClassIndex()), lowerByte(getClassIndex()), upperByte(getNameAndTypeIndex()), lowerByte(getNameAndTypeIndex())};
|
return new byte[]{TAG, upperByte(getClassIndex()), lowerByte(getClassIndex()), upperByte(getNameAndTypeIndex()), lowerByte(getNameAndTypeIndex())};
|
||||||
|
|
|
||||||
48
src/test/java/nl/sander/beejava/e2e/BeanWithMethodsTest.java
Normal file
48
src/test/java/nl/sander/beejava/e2e/BeanWithMethodsTest.java
Normal file
|
|
@ -0,0 +1,48 @@
|
||||||
|
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.Method;
|
||||||
|
import java.lang.reflect.Modifier;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.*;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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 BeanWithMethodsTest {
|
||||||
|
@Test
|
||||||
|
public void testEmptyBean() throws Exception {
|
||||||
|
// Arrange
|
||||||
|
BeeSource emptyClass = TestData.createClassWithTwoReferencesToSomeClass();
|
||||||
|
|
||||||
|
// Act
|
||||||
|
CompiledClass compiledClass = Compiler.compile(emptyClass);
|
||||||
|
byte[] bytecode = BytecodeGenerator.generate(compiledClass);
|
||||||
|
|
||||||
|
ByteClassLoader classLoader = new ByteClassLoader();
|
||||||
|
classLoader.setByteCode("nl.sander.beejava.test.ClassWithReferences", bytecode);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Class<?> classWithReferences = classLoader.loadClass("nl.sander.beejava.test.ClassWithReferences");
|
||||||
|
assertNotNull(classWithReferences);
|
||||||
|
|
||||||
|
Method[] methods = classWithReferences.getDeclaredMethods();
|
||||||
|
assertEquals(2, methods.length);
|
||||||
|
assertEquals("print1", methods[0].getName());
|
||||||
|
assertTrue(Modifier.isPublic(methods[0].getModifiers()));
|
||||||
|
assertEquals(0,methods[0].getParameterCount());
|
||||||
|
|
||||||
|
assertEquals("print2", methods[1].getName()); // ordering may cause failures
|
||||||
|
assertTrue(Modifier.isPublic(methods[1].getModifiers()));
|
||||||
|
assertEquals(0,methods[1].getParameterCount());
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Add table
Reference in a new issue