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:
Sander Hautvast 2020-11-18 18:45:08 +01:00
parent 282605ac7d
commit 7412b7ba43
13 changed files with 177 additions and 40 deletions

View file

@ -54,4 +54,6 @@ public abstract class CodeContainer {
}
this.owner = beeSource;
}
public abstract boolean isConstructor();
}

View file

@ -40,7 +40,7 @@ public class CompiledClass {
}
}
Set<ConstantPoolEntry> getConstantTree() {
public Set<ConstantPoolEntry> getConstantTree() {
return constantTree;
}

View file

@ -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);
}

View 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;
}
}

View file

@ -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;
}

View file

@ -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) {
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;
}
}

View file

@ -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) {

View file

@ -0,0 +1,5 @@
package nl.sander.beejava;
public enum StackOp {
PUSH,POP
}

View file

@ -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;

View file

@ -37,6 +37,11 @@ public final class BeeMethod extends CodeContainer {
return returnType;
}
@Override
public boolean isConstructor() {
return false;
}
public void validate() {
//TODO
/*

View file

@ -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;
}
}

View file

@ -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())};

View 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());
}
}