diff --git a/src/main/java/nl/sander/beejava/CodeContainer.java b/src/main/java/nl/sander/beejava/CodeContainer.java index 63404da..593d013 100644 --- a/src/main/java/nl/sander/beejava/CodeContainer.java +++ b/src/main/java/nl/sander/beejava/CodeContainer.java @@ -54,4 +54,6 @@ public abstract class CodeContainer { } this.owner = beeSource; } + + public abstract boolean isConstructor(); } diff --git a/src/main/java/nl/sander/beejava/CompiledClass.java b/src/main/java/nl/sander/beejava/CompiledClass.java index df75aba..18cbfc5 100644 --- a/src/main/java/nl/sander/beejava/CompiledClass.java +++ b/src/main/java/nl/sander/beejava/CompiledClass.java @@ -40,7 +40,7 @@ public class CompiledClass { } } - Set getConstantTree() { + public Set getConstantTree() { return constantTree; } diff --git a/src/main/java/nl/sander/beejava/Compiler.java b/src/main/java/nl/sander/beejava/Compiler.java index 6308a75..dfd2b98 100644 --- a/src/main/java/nl/sander/beejava/Compiler.java +++ b/src/main/java/nl/sander/beejava/Compiler.java @@ -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); } diff --git a/src/main/java/nl/sander/beejava/JavaOpcode.java b/src/main/java/nl/sander/beejava/JavaOpcode.java new file mode 100644 index 0000000..e581f73 --- /dev/null +++ b/src/main/java/nl/sander/beejava/JavaOpcode.java @@ -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; + } +} diff --git a/src/main/java/nl/sander/beejava/JavaOpcodes.java b/src/main/java/nl/sander/beejava/JavaOpcodes.java deleted file mode 100644 index 53e68b1..0000000 --- a/src/main/java/nl/sander/beejava/JavaOpcodes.java +++ /dev/null @@ -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; - -} diff --git a/src/main/java/nl/sander/beejava/MethodCodeCreator.java b/src/main/java/nl/sander/beejava/MethodCodeCreator.java index 1a330d2..7535dc5 100644 --- a/src/main/java/nl/sander/beejava/MethodCodeCreator.java +++ b/src/main/java/nl/sander/beejava/MethodCodeCreator.java @@ -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 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; + } } diff --git a/src/main/java/nl/sander/beejava/OpcodeMapper.java b/src/main/java/nl/sander/beejava/OpcodeMapper.java index a464a67..cfd83f8 100644 --- a/src/main/java/nl/sander/beejava/OpcodeMapper.java +++ b/src/main/java/nl/sander/beejava/OpcodeMapper.java @@ -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) { diff --git a/src/main/java/nl/sander/beejava/StackOp.java b/src/main/java/nl/sander/beejava/StackOp.java new file mode 100644 index 0000000..1446dba --- /dev/null +++ b/src/main/java/nl/sander/beejava/StackOp.java @@ -0,0 +1,5 @@ +package nl.sander.beejava; + +public enum StackOp { + PUSH,POP +} diff --git a/src/main/java/nl/sander/beejava/api/BeeConstructor.java b/src/main/java/nl/sander/beejava/api/BeeConstructor.java index bd2e761..e19b7aa 100644 --- a/src/main/java/nl/sander/beejava/api/BeeConstructor.java +++ b/src/main/java/nl/sander/beejava/api/BeeConstructor.java @@ -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; diff --git a/src/main/java/nl/sander/beejava/api/BeeMethod.java b/src/main/java/nl/sander/beejava/api/BeeMethod.java index 7c7ad34..63bb13f 100644 --- a/src/main/java/nl/sander/beejava/api/BeeMethod.java +++ b/src/main/java/nl/sander/beejava/api/BeeMethod.java @@ -37,6 +37,11 @@ public final class BeeMethod extends CodeContainer { return returnType; } + @Override + public boolean isConstructor() { + return false; + } + public void validate() { //TODO /* diff --git a/src/main/java/nl/sander/beejava/api/CodeLine.java b/src/main/java/nl/sander/beejava/api/CodeLine.java index 9e2f95f..3565e73 100644 --- a/src/main/java/nl/sander/beejava/api/CodeLine.java +++ b/src/main/java/nl/sander/beejava/api/CodeLine.java @@ -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; + } } diff --git a/src/main/java/nl/sander/beejava/constantpool/entry/MethodRefEntry.java b/src/main/java/nl/sander/beejava/constantpool/entry/MethodRefEntry.java index ebefd5b..d2263b5 100644 --- a/src/main/java/nl/sander/beejava/constantpool/entry/MethodRefEntry.java +++ b/src/main/java/nl/sander/beejava/constantpool/entry/MethodRefEntry.java @@ -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())}; diff --git a/src/test/java/nl/sander/beejava/e2e/BeanWithMethodsTest.java b/src/test/java/nl/sander/beejava/e2e/BeanWithMethodsTest.java new file mode 100644 index 0000000..905303f --- /dev/null +++ b/src/test/java/nl/sander/beejava/e2e/BeanWithMethodsTest.java @@ -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()); + + } +}