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;
|
||||
}
|
||||
|
||||
public abstract boolean isConstructor();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -40,7 +40,7 @@ public class CompiledClass {
|
|||
}
|
||||
}
|
||||
|
||||
Set<ConstantPoolEntry> getConstantTree() {
|
||||
public Set<ConstantPoolEntry> getConstantTree() {
|
||||
return constantTree;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -68,8 +68,7 @@ public class Compiler {
|
|||
this.codeAttributeNameEntry = constantPoolEntryCreator.getOrCreateUtf8("Code");
|
||||
compiledClass.addConstantPoolEntry(codeAttributeNameEntry);
|
||||
|
||||
ConstantPool constantPool = constantPoolCreator.createConstantPool(compiledClass.getConstantTree());
|
||||
return constantPool;
|
||||
return constantPoolCreator.createConstantPool(compiledClass.getConstantTree());
|
||||
}
|
||||
|
||||
public void addConstructors() {
|
||||
|
|
@ -98,13 +97,15 @@ public class Compiler {
|
|||
constantPoolEntryCreator.getOrCreateUtf8(method.getName()),
|
||||
constantPoolEntryCreator.getOrCreateUtf8(method.getSignature()))
|
||||
.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
|
||||
*/
|
||||
private void updateConstantPool(CodeContainer codeContainer) {
|
||||
compiledClass.addConstantPoolEntry(constantPoolEntryCreator.getOrCreateUtf8(codeContainer.getName()));
|
||||
compiledClass.addConstantPoolEntry(constantPoolEntryCreator.getOrCreateUtf8(codeContainer.getSignature()));
|
||||
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.classinfo.attributes.CodeAttribute;
|
||||
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.util.ByteBuf;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
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.setMaxStack(1);
|
||||
codeAttribute.setMaxLocals(1);
|
||||
codeAttribute.setMaxStack(calculateMaxStack(codeContainer));
|
||||
codeAttribute.setMaxLocals(codeContainer.formalParameters.size() + 1);
|
||||
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();
|
||||
byteBuf.addU8(OpcodeMapper.mapOpcode(codeLine));
|
||||
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());
|
||||
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.Modifier;
|
||||
|
||||
import static nl.sander.beejava.JavaOpcodes.*;
|
||||
import static nl.sander.beejava.JavaOpcode.*;
|
||||
|
||||
public class OpcodeMapper {
|
||||
|
||||
public static int mapOpcode(CodeLine codeLine) {
|
||||
public static JavaOpcode 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;
|
||||
case RETURN -> JavaOpcode.RETURN; //TODO not complete yet
|
||||
default -> throw new IllegalStateException("something not implemented");
|
||||
};
|
||||
}
|
||||
|
||||
/* TODO cover more cases */
|
||||
private static int invoke(CodeLine codeLine) {
|
||||
private static JavaOpcode invoke(CodeLine codeLine) {
|
||||
BeeSource source = codeLine.getOwner().getOwner();
|
||||
if (source.getAccessFlags().contains(ClassAccessFlags.SUPER)) {
|
||||
if (source.getAccessFlags().contains(ClassAccessFlags.SUPER) && codeLine.getOwner().isConstructor()) {
|
||||
return INVOKESPECIAL;
|
||||
} else {
|
||||
return INVOKEVIRTUAL;
|
||||
}
|
||||
}
|
||||
|
||||
private static int loadConst(CodeLine codeLine) {
|
||||
private static JavaOpcode loadConst(CodeLine codeLine) {
|
||||
Object constValue = codeLine.getConstValue();
|
||||
int index = codeLine.getAssignedEntry().getIndex();
|
||||
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;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isConstructor() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
|
|
|
|||
|
|
@ -37,6 +37,11 @@ public final class BeeMethod extends CodeContainer {
|
|||
return returnType;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isConstructor() {
|
||||
return false;
|
||||
}
|
||||
|
||||
public void validate() {
|
||||
//TODO
|
||||
/*
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ package nl.sander.beejava.api;
|
|||
|
||||
|
||||
import nl.sander.beejava.CodeContainer;
|
||||
import nl.sander.beejava.JavaOpcode;
|
||||
import nl.sander.beejava.TypeMapper;
|
||||
import nl.sander.beejava.constantpool.entry.ConstantPoolEntry;
|
||||
|
||||
|
|
@ -22,11 +23,10 @@ public final class CodeLine {
|
|||
private String outputSignature;
|
||||
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 Object constValue;
|
||||
|
||||
private ConstantPoolEntry assignedEntry;
|
||||
private CodeContainer owner;
|
||||
private JavaOpcode javaOpcode;
|
||||
|
||||
CodeLine(int linenumber, Opcode opcode) {
|
||||
this.linenumber = linenumber;
|
||||
|
|
@ -241,4 +241,12 @@ public final class CodeLine {
|
|||
public void setOwner(CodeContainer 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 NameAndTypeEntry nameAndType;
|
||||
|
||||
|
||||
public MethodRefEntry(ClassEntry classRef, NameAndTypeEntry nameAndType) {
|
||||
super(classRef, nameAndType);
|
||||
this.classRef = classRef;
|
||||
|
|
@ -22,6 +23,9 @@ public class MethodRefEntry extends ConstantPoolEntry {
|
|||
return nameAndType.getIndex();
|
||||
}
|
||||
|
||||
public NameAndTypeEntry getNameAndType() {
|
||||
return nameAndType;
|
||||
}
|
||||
|
||||
public byte[] getBytes() {
|
||||
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