From 9aad0e55c659bd1ba110d3dfe969350d7b7d3096 Mon Sep 17 00:00:00 2001 From: Sander Hautvast Date: Mon, 9 Nov 2020 09:56:33 +0100 Subject: [PATCH] API draft, constant pool creator working in principle. A working test --- .gitignore | 4 + ...nder.beejava.testclasses.IntBean_javap.txt | 54 ++++++++ pom.xml | 50 +++++++ src/main/java/nl/sander/beejava/BeeClass.java | 123 ++++++++++++++++++ .../nl/sander/beejava/BeeConstructor.java | 85 ++++++++++++ src/main/java/nl/sander/beejava/BeeField.java | 79 +++++++++++ .../java/nl/sander/beejava/BeePackage.java | 10 ++ .../java/nl/sander/beejava/BeeParameter.java | 38 ++++++ src/main/java/nl/sander/beejava/CodeLine.java | 107 +++++++++++++++ src/main/java/nl/sander/beejava/Compiler.java | 38 ++++++ .../sander/beejava/ConstantPoolCreator.java | 47 +++++++ .../sander/beejava/ConstantTreeCreator.java | 78 +++++++++++ .../java/nl/sander/beejava/ContainsCode.java | 8 ++ src/main/java/nl/sander/beejava/Opcode.java | 69 ++++++++++ src/main/java/nl/sander/beejava/Output.java | 22 ++++ src/main/java/nl/sander/beejava/Ref.java | 10 ++ .../java/nl/sander/beejava/TypeMapper.java | 20 +++ src/main/java/nl/sander/beejava/Version.java | 34 +++++ .../beejava/constantpool/ConstantPool.java | 31 +++++ .../constantpool/entry/ClassEntry.java | 21 +++ .../constantpool/entry/ConstantPoolEntry.java | 27 ++++ .../constantpool/entry/DoubleEntry.java | 10 ++ .../constantpool/entry/FieldRefEntry.java | 28 ++++ .../constantpool/entry/FloatEntry.java | 16 +++ .../beejava/constantpool/entry/IntEntry.java | 16 +++ .../entry/InterfaceMethodRefEntry.java | 20 +++ .../entry/InvokeDynamicEntry.java | 17 +++ .../beejava/constantpool/entry/LeafEntry.java | 11 ++ .../beejava/constantpool/entry/LongEntry.java | 17 +++ .../constantpool/entry/MethodHandleEntry.java | 21 +++ .../constantpool/entry/MethodRefEntry.java | 21 +++ .../constantpool/entry/MethodTypeEntry.java | 18 +++ .../constantpool/entry/ModuleEntry.java | 11 ++ .../constantpool/entry/NameAndTypeEntry.java | 29 +++++ .../constantpool/entry/PackageEntry.java | 14 ++ .../constantpool/entry/StringEntry.java | 20 +++ .../beejava/constantpool/entry/Utf8Entry.java | 20 +++ .../nl/sander/beejava/flags/AccessFlag.java | 12 ++ .../sander/beejava/flags/ClassAccessFlag.java | 26 ++++ .../sander/beejava/flags/FieldAccessFlag.java | 25 ++++ .../beejava/flags/MethodAccessFlag.java | 28 ++++ .../java/nl/sander/beejava/util/ByteBuf.java | 86 ++++++++++++ .../nl/sander/beejava/ConstantTeeTests.java | 114 ++++++++++++++++ .../sander/beejava/testclasses/IntBean.java | 10 ++ 44 files changed, 1545 insertions(+) create mode 100644 .gitignore create mode 100644 javap/nl.sander.beejava.testclasses.IntBean_javap.txt create mode 100644 pom.xml create mode 100644 src/main/java/nl/sander/beejava/BeeClass.java create mode 100644 src/main/java/nl/sander/beejava/BeeConstructor.java create mode 100644 src/main/java/nl/sander/beejava/BeeField.java create mode 100644 src/main/java/nl/sander/beejava/BeePackage.java create mode 100644 src/main/java/nl/sander/beejava/BeeParameter.java create mode 100644 src/main/java/nl/sander/beejava/CodeLine.java create mode 100644 src/main/java/nl/sander/beejava/Compiler.java create mode 100644 src/main/java/nl/sander/beejava/ConstantPoolCreator.java create mode 100644 src/main/java/nl/sander/beejava/ConstantTreeCreator.java create mode 100644 src/main/java/nl/sander/beejava/ContainsCode.java create mode 100644 src/main/java/nl/sander/beejava/Opcode.java create mode 100644 src/main/java/nl/sander/beejava/Output.java create mode 100644 src/main/java/nl/sander/beejava/Ref.java create mode 100644 src/main/java/nl/sander/beejava/TypeMapper.java create mode 100644 src/main/java/nl/sander/beejava/Version.java create mode 100644 src/main/java/nl/sander/beejava/constantpool/ConstantPool.java create mode 100644 src/main/java/nl/sander/beejava/constantpool/entry/ClassEntry.java create mode 100644 src/main/java/nl/sander/beejava/constantpool/entry/ConstantPoolEntry.java create mode 100644 src/main/java/nl/sander/beejava/constantpool/entry/DoubleEntry.java create mode 100644 src/main/java/nl/sander/beejava/constantpool/entry/FieldRefEntry.java create mode 100644 src/main/java/nl/sander/beejava/constantpool/entry/FloatEntry.java create mode 100644 src/main/java/nl/sander/beejava/constantpool/entry/IntEntry.java create mode 100644 src/main/java/nl/sander/beejava/constantpool/entry/InterfaceMethodRefEntry.java create mode 100644 src/main/java/nl/sander/beejava/constantpool/entry/InvokeDynamicEntry.java create mode 100644 src/main/java/nl/sander/beejava/constantpool/entry/LeafEntry.java create mode 100644 src/main/java/nl/sander/beejava/constantpool/entry/LongEntry.java create mode 100644 src/main/java/nl/sander/beejava/constantpool/entry/MethodHandleEntry.java create mode 100644 src/main/java/nl/sander/beejava/constantpool/entry/MethodRefEntry.java create mode 100644 src/main/java/nl/sander/beejava/constantpool/entry/MethodTypeEntry.java create mode 100644 src/main/java/nl/sander/beejava/constantpool/entry/ModuleEntry.java create mode 100644 src/main/java/nl/sander/beejava/constantpool/entry/NameAndTypeEntry.java create mode 100644 src/main/java/nl/sander/beejava/constantpool/entry/PackageEntry.java create mode 100644 src/main/java/nl/sander/beejava/constantpool/entry/StringEntry.java create mode 100644 src/main/java/nl/sander/beejava/constantpool/entry/Utf8Entry.java create mode 100644 src/main/java/nl/sander/beejava/flags/AccessFlag.java create mode 100644 src/main/java/nl/sander/beejava/flags/ClassAccessFlag.java create mode 100644 src/main/java/nl/sander/beejava/flags/FieldAccessFlag.java create mode 100644 src/main/java/nl/sander/beejava/flags/MethodAccessFlag.java create mode 100644 src/main/java/nl/sander/beejava/util/ByteBuf.java create mode 100644 src/test/java/nl/sander/beejava/ConstantTeeTests.java create mode 100644 src/test/java/nl/sander/beejava/testclasses/IntBean.java diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..cd6b05e --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +.idea/ +target/ +.DS_Store +*.iml diff --git a/javap/nl.sander.beejava.testclasses.IntBean_javap.txt b/javap/nl.sander.beejava.testclasses.IntBean_javap.txt new file mode 100644 index 0000000..e3cef4b --- /dev/null +++ b/javap/nl.sander.beejava.testclasses.IntBean_javap.txt @@ -0,0 +1,54 @@ +Classfile /Users/Shautvast/IdeaProjects/beejava/target/test-classes/nl/sander/beejava/testclasses/IntBean.class + Last modified 2 Oct 2020; size 369 bytes + SHA-256 checksum ef66d886f52e768d038ec9e87724671c6a748ea0cd62b1b117c5cfb31bcdb153 + Compiled from "IntBean.java" +public class nl.sander.beejava.testclasses.IntBean + minor version: 0 + major version: 58 + flags: (0x0021) ACC_PUBLIC, ACC_SUPER + this_class: #8 // nl/sander/beejava/testclasses/IntBean + super_class: #2 // java/lang/Object + interfaces: 0, fields: 1, methods: 1, attributes: 1 +Constant pool: + #1 = Methodref #2.#3 // java/lang/Object."":()V + #2 = Class #4 // java/lang/Object + #3 = NameAndType #5:#6 // "":()V + #4 = Utf8 java/lang/Object + #5 = Utf8 + #6 = Utf8 ()V + #7 = Fieldref #8.#9 // nl/sander/beejava/testclasses/IntBean.intField:I + #8 = Class #10 // nl/sander/beejava/testclasses/IntBean + #9 = NameAndType #11:#12 // intField:I + #10 = Utf8 nl/sander/beejava/testclasses/IntBean + #11 = Utf8 intField + #12 = Utf8 I + #13 = Utf8 (I)V + #14 = Utf8 Code + #15 = Utf8 LineNumberTable + #16 = Utf8 LocalVariableTable + #17 = Utf8 this + #18 = Utf8 Lnl/sander/beejava/testclasses/IntBean; + #19 = Utf8 SourceFile + #20 = Utf8 IntBean.java +{ + public nl.sander.beejava.testclasses.IntBean(int); + descriptor: (I)V + flags: (0x0001) ACC_PUBLIC + Code: + stack=2, locals=2, args_size=2 + 0: aload_0 + 1: invokespecial #1 // Method java/lang/Object."":()V + 4: aload_0 + 5: iload_1 + 6: putfield #7 // Field intField:I + 9: return + LineNumberTable: + line 6: 0 + line 7: 4 + line 8: 9 + LocalVariableTable: + Start Length Slot Name Signature + 0 10 0 this Lnl/sander/beejava/testclasses/IntBean; + 0 10 1 intField I +} +SourceFile: "IntBean.java" diff --git a/pom.xml b/pom.xml new file mode 100644 index 0000000..af08da6 --- /dev/null +++ b/pom.xml @@ -0,0 +1,50 @@ + + 4.0.0 + + beejava + + + + org.apache.maven.plugins + maven-compiler-plugin + + 14 + 14 + + + + + nl.sander + beejava + 0.1-SNAPSHOT + jar + + + 15 + 15 + UTF-8 + + + + + org.junit.jupiter + junit-jupiter-engine + 5.6.2 + test + + + org.assertj + assertj-core + 1.6.0 + test + + + org.mockito + mockito-all + 1.10.19 + test + + + + diff --git a/src/main/java/nl/sander/beejava/BeeClass.java b/src/main/java/nl/sander/beejava/BeeClass.java new file mode 100644 index 0000000..e85f332 --- /dev/null +++ b/src/main/java/nl/sander/beejava/BeeClass.java @@ -0,0 +1,123 @@ +package nl.sander.beejava; + +import nl.sander.beejava.flags.ClassAccessFlag; + +import java.util.Arrays; +import java.util.HashSet; +import java.util.Set; + +public class BeeClass { + private final Version classFileVersion; + private final BeePackage beePackage; + private final Set accessFlags = new HashSet<>(); + private final String name; + private final Class superClass; + private final Set> interfaces = new HashSet<>(); + private final Set fields = new HashSet<>(); + private final Set constructors = new HashSet<>(); + + private BeeClass(Version classFileVersion, + BeePackage beePackage, Set accessFlags, String name, Class superClass, + Set> interfaces, Set fields, Set constructors) { + this.classFileVersion = classFileVersion; + this.beePackage = beePackage; + this.accessFlags.addAll(accessFlags); + this.name = name; + this.superClass = superClass; + this.interfaces.addAll(interfaces); + this.fields.addAll(fields); + this.constructors.addAll(constructors); + } + + public static BeeClass.Builder builder() { + return new Builder(); + } + + public Version getClassFileVersion() { + return classFileVersion; + } + + public BeePackage getPackage() { + return beePackage; + } + + public String getName() { + return name; + } + + public Set getConstructors() { + return constructors; + } + + public Set getAccessFlags() { + return accessFlags; + } + + public Class getSuperClass() { + return superClass; + } + + public Set getFields() { + return fields; + } + + public static class Builder { + private Version version; + private final Set accessFlags = new HashSet<>(); + private final Set> interfaces = new HashSet<>(); + private final Set fields = new HashSet<>(); + private final Set constructors = new HashSet<>(); + private BeePackage beePackage; + private Class superClass = Object.class; + private String name; + + private Builder() { + } + + public Builder withClassFileVersion(Version version){ + this.version=version; + return this; + } + + public BeeClass.Builder withPackage(String beePackage) { + this.beePackage = new BeePackage(beePackage); + return this; + } + + public BeeClass.Builder withAccessFlags(ClassAccessFlag... accessFlags) { + this.accessFlags.addAll(Arrays.asList(accessFlags)); + return this; + } + + public BeeClass.Builder withName(String name) { + this.name = name; + return this; + } + + public Builder withSuperClass(Class superClass) { + this.superClass = superClass; + return this; + } + + public Builder withInterfaces(Class... interfaces) { + this.interfaces.addAll(Arrays.asList(interfaces)); + return this; + } + + public Builder withFields(BeeField... fields) { + this.fields.addAll(Arrays.asList(fields)); + return this; + } + + public Builder withConstructors(BeeConstructor... constructors) { + this.constructors.addAll(Arrays.asList(constructors)); + return this; + } + + public BeeClass build() { + return new BeeClass(version, beePackage, accessFlags, name, superClass, interfaces, fields, constructors); + } + + + } +} diff --git a/src/main/java/nl/sander/beejava/BeeConstructor.java b/src/main/java/nl/sander/beejava/BeeConstructor.java new file mode 100644 index 0000000..a5243e5 --- /dev/null +++ b/src/main/java/nl/sander/beejava/BeeConstructor.java @@ -0,0 +1,85 @@ +package nl.sander.beejava; + +import nl.sander.beejava.flags.MethodAccessFlag; + +import java.util.*; + +public class BeeConstructor implements ContainsCode{ + private final Set accessFlags = new HashSet<>(); + private final Set formalParameters = new HashSet<>(); + private final List code = new LinkedList<>(); + + private BeeConstructor(Set accessFlags, + List formalParameters, + List code) { + this.formalParameters.addAll(formalParameters); + this.accessFlags.addAll(accessFlags); + this.code.addAll(code); + } + + public static Builder builder() { + return new Builder(); + } + + Set getAccessFlags() { + return accessFlags; + } + + Set getFormalParameters() { + return formalParameters; + } + + @Override + public List getCode() { + return code; + } + + @Override + public String toString() { + return "BeeConstructor{" + + "formalParameters=" + formalParameters + + '}'; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + BeeConstructor that = (BeeConstructor) o; + return formalParameters.equals(that.formalParameters); + } + + @Override + public int hashCode() { + return Objects.hash(formalParameters); + } + + public static class Builder { + private final Set accessFlags = new HashSet<>(); + private final List formalParameters = new LinkedList<>(); + private final List code = new LinkedList<>(); + + private Builder() { + + } + + public Builder withFormalParameters(BeeParameter... formalParameters) { + this.formalParameters.addAll(Arrays.asList(formalParameters)); + return this; + } + + public Builder withAccessFlags(MethodAccessFlag... accessFlags) { + this.accessFlags.addAll(Arrays.asList(accessFlags)); + return this; + } + + public Builder withCode(CodeLine... lines) { + this.code.addAll(Arrays.asList(lines)); + return this; + } + + public BeeConstructor build() { + return new BeeConstructor(accessFlags, formalParameters, code); + } + } +} diff --git a/src/main/java/nl/sander/beejava/BeeField.java b/src/main/java/nl/sander/beejava/BeeField.java new file mode 100644 index 0000000..138fb64 --- /dev/null +++ b/src/main/java/nl/sander/beejava/BeeField.java @@ -0,0 +1,79 @@ +package nl.sander.beejava; + +import nl.sander.beejava.flags.FieldAccessFlag; + +import java.util.Arrays; +import java.util.HashSet; +import java.util.Objects; +import java.util.Set; + +public class BeeField { + + private final Set accessFlags = new HashSet<>(); + private final Class type; + private final String name; + + private BeeField(Set accessFlags, Class type, String name) { + this.accessFlags.addAll(accessFlags); + this.type = type; + this.name = name; + } + + public static BeeField.Builder builder(){ + return new Builder(); + } + + public Set getAccessFlags() { + return accessFlags; + } + + public Class getType() { + return type; + } + + public String getName() { + return name; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + BeeField beeField = (BeeField) o; + return name.equals(beeField.name); + } + + @Override + public int hashCode() { + return Objects.hash(name); + } + + public static class Builder { + private final Set accessFlags = new HashSet<>(); + private Class type; + private String name; + + private Builder(){ + + } + + public BeeField.Builder withAccessFlags(FieldAccessFlag... accessFlags) { + this.accessFlags.addAll(Arrays.asList(accessFlags)); + return this; + } + + public BeeField.Builder withType(Class type) { + this.type=type; + return this; + } + + public BeeField.Builder withName(String name) { + this.name=name; + return this; + } + + public BeeField build() { + return new BeeField(accessFlags, type, name); + } + } +} diff --git a/src/main/java/nl/sander/beejava/BeePackage.java b/src/main/java/nl/sander/beejava/BeePackage.java new file mode 100644 index 0000000..1d655ed --- /dev/null +++ b/src/main/java/nl/sander/beejava/BeePackage.java @@ -0,0 +1,10 @@ +package nl.sander.beejava; + +class BeePackage { + + private final String name; + + BeePackage(String name) { + this.name = name; + } +} diff --git a/src/main/java/nl/sander/beejava/BeeParameter.java b/src/main/java/nl/sander/beejava/BeeParameter.java new file mode 100644 index 0000000..60c3604 --- /dev/null +++ b/src/main/java/nl/sander/beejava/BeeParameter.java @@ -0,0 +1,38 @@ +package nl.sander.beejava; + +import java.util.Objects; + +public class BeeParameter { + private final Class type; + private final String name; + + private BeeParameter(Class type, String name) { + this.type = type; + this.name = name; + } + + public static BeeParameter create(Class type, String name) { + return new BeeParameter(type, Objects.requireNonNull(name)); + } + + public Class getType() { + return type; + } + + public String getName() { + return name; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + BeeParameter that = (BeeParameter) o; + return name.equals(that.name); + } + + @Override + public int hashCode() { + return Objects.hash(name); + } +} diff --git a/src/main/java/nl/sander/beejava/CodeLine.java b/src/main/java/nl/sander/beejava/CodeLine.java new file mode 100644 index 0000000..a0bbb5d --- /dev/null +++ b/src/main/java/nl/sander/beejava/CodeLine.java @@ -0,0 +1,107 @@ +package nl.sander.beejava; + + +class CodeLine { + private final int linenumber; + private final Opcode opcode; + private Ref ref; + private BeeParameter parameter; + private String methodName; + private String inputSignature; + private String outputSignature; + private BeeField field; + + CodeLine(int linenumber, Opcode opcode) { + this.linenumber = linenumber; + this.opcode = opcode; + } + + public static CodeLine line(int nr, Opcode opcode, Ref ref) { + return new CodeLine(nr, opcode).withRef(ref); + } + + 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(); + } + + 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) { + return new CodeLine(nr, opcode); + } + + public static CodeLine line(int nr, Opcode opcode, BeeParameter parameter) { + return new CodeLine(nr, opcode).withParameter(parameter); + } + + public static CodeLine line(int nr, Opcode opcode, BeeField intField) { + return new CodeLine(nr, opcode).withRef(Ref.THIS).withField(intField); + } + + private CodeLine withRef(Ref ref) { + this.ref = ref; + return this; + } + + private CodeLine withMethodName(String methodName) { + this.methodName = methodName; + return this; + } + + private CodeLine withInput(String inputSignature) { + this.inputSignature = inputSignature; + return this; + } + + private CodeLine withVoidOutput() { + return withOutput("V"); + } + + private CodeLine withOutput(String outputSignature) { + this.outputSignature = outputSignature; + return this; + } + + private CodeLine withParameter(BeeParameter parameter) { + this.parameter = parameter; + return this; + } + + private CodeLine withField(BeeField field) { + this.field = field; + return this; + } + + public String getMethodName() { + return methodName; + } + + boolean hasMethod() { + return methodName != null; + } + + String getMethodSignature() { + return inputSignature + outputSignature; + } + + Ref getRef() { + return ref; + } + + + boolean hasField() { + return field != null; + } + + BeeField getField() { + return field; + } + + BeeParameter getParameter() { + return parameter; + } + + +} diff --git a/src/main/java/nl/sander/beejava/Compiler.java b/src/main/java/nl/sander/beejava/Compiler.java new file mode 100644 index 0000000..d86ae33 --- /dev/null +++ b/src/main/java/nl/sander/beejava/Compiler.java @@ -0,0 +1,38 @@ +package nl.sander.beejava; + +import nl.sander.beejava.constantpool.ConstantPool; +import nl.sander.beejava.constantpool.entry.ConstantPoolEntry; +import nl.sander.beejava.util.ByteBuf; + +import java.util.Set; + +public class Compiler { + + private final BeeClass beeClass; // maybe not a member? + private final ConstantTreeCreator constantTreeCreator = new ConstantTreeCreator(); + private final ConstantPoolCreator constantPoolCreator = new ConstantPoolCreator(); + + public Compiler(BeeClass beeClass) { + this.beeClass = beeClass; + } + + public static byte[] compile(BeeClass beeClass) { + Compiler compiler = new Compiler(beeClass); + return compiler.doCompile(); + } + + private byte[] doCompile() { + ByteBuf buf = new ByteBuf(); + buf.add(0xCA, 0xFE, 0xBA, 0xBE); + buf.add(beeClass.getClassFileVersion().getMinor()); + buf.add(beeClass.getClassFileVersion().getMajor()); + + Set constantTree = constantTreeCreator.createConstantTree(beeClass); + ConstantPool constantPool = constantPoolCreator.createConstantPool(constantTree); + + buf.add(constantPool.getBytes()); + return buf.toBytes(); + } + + +} diff --git a/src/main/java/nl/sander/beejava/ConstantPoolCreator.java b/src/main/java/nl/sander/beejava/ConstantPoolCreator.java new file mode 100644 index 0000000..c77b38b --- /dev/null +++ b/src/main/java/nl/sander/beejava/ConstantPoolCreator.java @@ -0,0 +1,47 @@ +package nl.sander.beejava; + +import nl.sander.beejava.constantpool.ConstantPool; +import nl.sander.beejava.constantpool.entry.ConstantPoolEntry; + +import java.util.Set; + +/** + * Transforms the hierachical constant tree into a flat datastructure. The walks the tree adding indexes to each element. + */ +public class ConstantPoolCreator { + private ConstantPool constantPool; + private int index; + + public ConstantPool createConstantPool(Set constantTree) { + constantPool = new ConstantPool(); + constantPool.add(null); // dummy element to align it's index with the indexes in the elements themselves + index = 0; + updateToplevelElements(constantTree); + return constantPool; + } + + private void updateToplevelElements(Set children) { + for (ConstantPoolEntry child : children) { + addToPool(child); + updateChildElements(child.getChildren()); + // first the complete toplevel element including it's children, then next toplevel element + } + } + + private void updateChildElements(Set children) { + // first all direct children + for (ConstantPoolEntry child : children) { + addToPool(child); + } + // then further lineage + for (ConstantPoolEntry child : children) { + updateChildElements(child.getChildren()); + } + } + + private void addToPool(ConstantPoolEntry entry) { + index += 1; + entry.setIndex(index); + constantPool.add(entry); + } +} diff --git a/src/main/java/nl/sander/beejava/ConstantTreeCreator.java b/src/main/java/nl/sander/beejava/ConstantTreeCreator.java new file mode 100644 index 0000000..32e88f5 --- /dev/null +++ b/src/main/java/nl/sander/beejava/ConstantTreeCreator.java @@ -0,0 +1,78 @@ +package nl.sander.beejava; + +import nl.sander.beejava.constantpool.entry.*; + +import java.util.LinkedHashSet; +import java.util.Set; + +/** + * Builds a set of a tree of constant pool entries that refer to each other. + * + * A client must supply a {@link BeeClass} containing a set of {@link CodeLine}s that is assumed to be correct. + * It doesn't check if a valid state is reached. + */ +/* So the name isn't entirely correct. Waiting for inspiration. + * also TODO make sure entries aren't duplicates + */ +public class ConstantTreeCreator { + private final Set constantTree = new LinkedHashSet<>(); + private BeeClass beeClass; + + public Set createConstantTree(BeeClass beeClass) { + constantTree.clear(); + this.beeClass = beeClass; + beeClass.getConstructors().forEach(this::updateConstantTree); + // TODO update constantTree for fields ? + // TODO update constantTree for methods + return constantTree; + } + + private void updateConstantTree(ContainsCode codeContainer) { + codeContainer.getCode().forEach(this::updateConstantTree); + } + + // TODO construct multi root tree + private void updateConstantTree(CodeLine codeline) { + if (codeline.hasMethod()){ + addMethodRef(codeline); + } + + if (codeline.hasField()) { + addFieldRef(codeline); + } + } + + private void addMethodRef(CodeLine codeline) { + constantTree.add(new MethodRefEntry(createClassName(codeline), createMethodNameAndType(codeline))); + } + + private void addFieldRef(CodeLine codeline) { + constantTree.add(new FieldRefEntry(createClassName(codeline), createFieldNameAndType(codeline))); + } + + private NameAndTypeEntry createMethodNameAndType(CodeLine codeline) { + return new NameAndTypeEntry(new Utf8Entry(codeline.getMethodName()), new Utf8Entry(codeline.getMethodSignature())); + } + + private NameAndTypeEntry createFieldNameAndType(CodeLine codeline) { + return new NameAndTypeEntry(new Utf8Entry(codeline.getField().getName()), new Utf8Entry(TypeMapper.map(codeline.getField().getType()))); + } + + private ClassEntry createClassName(CodeLine codeline) { + return new ClassEntry(new Utf8Entry(internalName(getNameOfClass(codeline)))); + } + + private String getNameOfClass(CodeLine codeline) { + if (codeline.getRef() == Ref.SUPER) { + return beeClass.getSuperClass().getName(); + } else if (codeline.getRef() == Ref.THIS) { + return beeClass.getPackage() + "." + beeClass.getName(); + } + throw new RuntimeException("shouldn't be here"); + } + + private String internalName(String name) { + return name.replaceAll("\\.", "/"); + } + +} diff --git a/src/main/java/nl/sander/beejava/ContainsCode.java b/src/main/java/nl/sander/beejava/ContainsCode.java new file mode 100644 index 0000000..fc762fe --- /dev/null +++ b/src/main/java/nl/sander/beejava/ContainsCode.java @@ -0,0 +1,8 @@ +package nl.sander.beejava; + +import java.util.List; + +public interface ContainsCode { + + List getCode(); +} diff --git a/src/main/java/nl/sander/beejava/Opcode.java b/src/main/java/nl/sander/beejava/Opcode.java new file mode 100644 index 0000000..263386b --- /dev/null +++ b/src/main/java/nl/sander/beejava/Opcode.java @@ -0,0 +1,69 @@ +package nl.sander.beejava; + +public enum Opcode { + LOAD("load"), + STORE("store"), + CONST("const"), + NEWARRAY("new"), + RETURN("return"), + ARRAYLENGTH("length"), + THROW("throw"), + PUSH("push"), + CHECKCAST("checkcast"), + ADD("add"), + COMPARE("cmp"), + DIVIDE("div"), + MULTIPLY("mul"), + NEGATE("neg"), + REMAINDER("rem"), + SUBTRACT("sub"), + DUPLICATE("dup"), + DUPLICATE_DOWN("dup_x1"), + DUPLICATE2("dup2"), + DUPLICATE2_DOWN("dup2_x1"), + GET("get"), + GOTO("goto"), + GOTO_W("goto_w"), + TO_DOUBLE("2d"), + TO_INT("2i"), + TO_LONG("2l"), + TO_BYTE("2b"), + TO_CHAR("2c"), + TO_FLOAT("2f"), + TO_SHORT("2s"), + AND("and"), + IF_EQUAL("ifeq"), + IF_NOT_EQUAL("ifneq"), + IF_LESS_THAN("iflt"), + IF_GREATER_OR_EQUAL("ifge"), + IF_GREATER_THAN("ifgt"), + IF_LESS_OR_EQUAL("ifle"), + IF_NOT_NULL("ifnotnull"), + IF_NULL("ifnull"), + INCREMENT("inc"), + INSTANCEOF("instanceof"), + INVOKE("invoke"), + OR("or"), + SHIFT_LEFT("shr"), + SHIFT_RIGHT("shl"), + LOGICAL_SHIFT_RIGHT("ushr"), + XOR("xor"), + LOOKUPSWITCH("lookupswitch"), + MONITORENTER("monitorenter"), + MONITOREXIT("monitorexit"), + MULTIANEWARRAY("multinewarray"), + NEW("new"), + NOP("nop"), + POP("pop"), + POP2("pop2"), + PUT("put"), + SWAP("swap"), + TABLESWITCH("tableswitch"), + WIDE("wide"); + + private final String name; + + Opcode(String name) { + this.name=name; + } +} diff --git a/src/main/java/nl/sander/beejava/Output.java b/src/main/java/nl/sander/beejava/Output.java new file mode 100644 index 0000000..34c7660 --- /dev/null +++ b/src/main/java/nl/sander/beejava/Output.java @@ -0,0 +1,22 @@ +package nl.sander.beejava; + +/** + * Only used to contain void return + */ +public final class Output { + + public static final Output VOID =new Output(); + + private Output() { + } + + @Override + public int hashCode() { + return getClass().hashCode(); + } + + @Override + public boolean equals(Object obj) { + return getClass() == obj.getClass(); + } +} diff --git a/src/main/java/nl/sander/beejava/Ref.java b/src/main/java/nl/sander/beejava/Ref.java new file mode 100644 index 0000000..ac972f1 --- /dev/null +++ b/src/main/java/nl/sander/beejava/Ref.java @@ -0,0 +1,10 @@ +package nl.sander.beejava; + +/** + * used to indicate this, super, or none of those, in which case it's class + */ +public enum Ref { + THIS, + SUPER, + CLASS +} diff --git a/src/main/java/nl/sander/beejava/TypeMapper.java b/src/main/java/nl/sander/beejava/TypeMapper.java new file mode 100644 index 0000000..35a62cc --- /dev/null +++ b/src/main/java/nl/sander/beejava/TypeMapper.java @@ -0,0 +1,20 @@ +package nl.sander.beejava; + +import java.util.Map; +import java.util.Optional; +import java.util.concurrent.ConcurrentHashMap; + +public class TypeMapper { + private static final Map, String> MAP = new ConcurrentHashMap<>(); + + static { + MAP.put(int.class, "I"); + } + + public static String map(Class type) { + return Optional.ofNullable(MAP.get(type)) + .orElseThrow(() -> new RuntimeException("Type " + type.getName() + " not found")); //this MUST not happen -> TODO map all types + } + + +} diff --git a/src/main/java/nl/sander/beejava/Version.java b/src/main/java/nl/sander/beejava/Version.java new file mode 100644 index 0000000..81ae9b0 --- /dev/null +++ b/src/main/java/nl/sander/beejava/Version.java @@ -0,0 +1,34 @@ +package nl.sander.beejava; + +public enum Version { + V1_0_2(45), + V1_1(45), + V1_2(46), + V1_3(47), + V1_4(48), + V5_0(49), + V6(50), + V7(51), + V8(52), + V9(53), + V10(54), + V11(55), + V12(56), + V13(57), + V14(58), + V15(59); + + private final int major; + + Version(int major) { + this.major = major; + } + + public int getMajor() { + return major; + } + + public int getMinor(){ + return 0; + } +} diff --git a/src/main/java/nl/sander/beejava/constantpool/ConstantPool.java b/src/main/java/nl/sander/beejava/constantpool/ConstantPool.java new file mode 100644 index 0000000..3af37e1 --- /dev/null +++ b/src/main/java/nl/sander/beejava/constantpool/ConstantPool.java @@ -0,0 +1,31 @@ +package nl.sander.beejava.constantpool; + +import nl.sander.beejava.constantpool.entry.ConstantPoolEntry; + +import java.util.ArrayList; +import java.util.List; + +public class ConstantPool { + private final List entries=new ArrayList<>(); + + public int getIndex(ConstantPoolEntry entry){ + for (int i=0; i accessflags) { + return accessflags.stream().mapToInt(AccessFlag::getBytecode).sum(); + } +} diff --git a/src/main/java/nl/sander/beejava/flags/ClassAccessFlag.java b/src/main/java/nl/sander/beejava/flags/ClassAccessFlag.java new file mode 100644 index 0000000..e67f38a --- /dev/null +++ b/src/main/java/nl/sander/beejava/flags/ClassAccessFlag.java @@ -0,0 +1,26 @@ +package nl.sander.beejava.flags; + +import nl.sander.beejava.flags.AccessFlag; + +public enum ClassAccessFlag implements AccessFlag { + PUBLIC(0x0001), // Declared public; may be accessed from outside its package. + FINAL(0x0010), // Declared final; no subclasses allowed. + SUPER(0x0020), // Treat superclass methods specially when invoked by the invokespecial instruction. + INTERFACE(0x0200), // Is an interface, not a class. + ABSTRACT(0x0400), // Declared abstract; must not be instantiated. + SYNTHETIC(0x1000), // Declared synthetic; not present in the source code. + ANNOTATION(0x2000), // Declared as an annotation type. + ENUM(0x4000), // Declared as an enum type. + MODULE(0x8000); // Is a module, not a class or interface. + + private final int bytecode; + + ClassAccessFlag(int bytecode) { + this.bytecode=bytecode; + } + + @Override + public int getBytecode() { + return bytecode; + } +} diff --git a/src/main/java/nl/sander/beejava/flags/FieldAccessFlag.java b/src/main/java/nl/sander/beejava/flags/FieldAccessFlag.java new file mode 100644 index 0000000..235e246 --- /dev/null +++ b/src/main/java/nl/sander/beejava/flags/FieldAccessFlag.java @@ -0,0 +1,25 @@ +package nl.sander.beejava.flags; + +import nl.sander.beejava.flags.AccessFlag; + +public enum FieldAccessFlag implements AccessFlag { + PUBLIC(0x0001), // Declared public; may be accessed from outside itspackage. + PRIVATE(0x0002), // Declared private; accessible only within the defining class and other classes belonging to the samenest (§5.4.4). + PROTECTED(0x0004), // Declared protected; may be accessed within subclasses. + STATIC(0x0008), // Declared static. + FINAL(0x0010), // Declared final; never directly assigned to afterobject construction (JLS §17.5). + VOLATILE(0x0040), // Declared volatile; cannot be cached. + ACC_TRANSIENT(0x0080), // Declared transient; not written or read by apersistent object manager. + SYNTHETIC(0x1000), // Declared synthetic; not present in the source code. + ENUM(0x4000); //Declared as an element of an enum. + + private final int bytecode; + + FieldAccessFlag(int bytecode) { + this.bytecode = bytecode; + } + + public int getBytecode() { + return bytecode; + } +} diff --git a/src/main/java/nl/sander/beejava/flags/MethodAccessFlag.java b/src/main/java/nl/sander/beejava/flags/MethodAccessFlag.java new file mode 100644 index 0000000..9136c0d --- /dev/null +++ b/src/main/java/nl/sander/beejava/flags/MethodAccessFlag.java @@ -0,0 +1,28 @@ +package nl.sander.beejava.flags; + +import nl.sander.beejava.flags.AccessFlag; + +public enum MethodAccessFlag implements AccessFlag { + PUBLIC(0x0001), // Declared public; may be accessed from outside its package. + PRIVATE(0x0002), // Declared private; accessible only within the defining class and other classes belonging to the same nest (§5.4.4). + PROTECTED(0x0004), // Declared protected; may be accessed within subclasses. + STATIC(0x0008), // Declared static. + FINAL(0x0010), // Declared final; must not be overridden (§5.4.5). + SYNCHRONIZED(0x0020), // Declared synchronized; invocation is wrapped by a monitor use. + BRIDGE(0x0040), // A bridge method, generated by the compiler. + VARARGS(0x0080), //Declared with variable number of arguments. + NATIVE(0x0100), //Declared native; implemented in a language other than the Java programming language. + ABSTRACT(0x0400), // Declared abstract; no implementation is provided. + STRICT(0x0800), // Declared strictfp; floating-point mode is FP-strict. + SYNTHETIC(0x1000); // Declared synthetic; not present in the source code. + + private final int bytecode; + + MethodAccessFlag(int bytecode) { + this.bytecode = bytecode; + } + + public int getBytecode() { + return bytecode; + } +} diff --git a/src/main/java/nl/sander/beejava/util/ByteBuf.java b/src/main/java/nl/sander/beejava/util/ByteBuf.java new file mode 100644 index 0000000..300bf5a --- /dev/null +++ b/src/main/java/nl/sander/beejava/util/ByteBuf.java @@ -0,0 +1,86 @@ +package nl.sander.beejava.util; + +import java.nio.ByteBuffer; +import java.nio.charset.*; + +/** + * storage like ArrayList, with bytebuffer instead of array + */ +public class ByteBuf { + + private ByteBuffer data; + + public ByteBuf() { + this(64); + } + + public ByteBuf(final int initialSize) { + data = ByteBuffer.allocate(initialSize); + } + + 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); + } + } + + public void clear() { + data.clear(); + } + + public void add(final byte c) { + if (data.remaining() == 0) { + enlarge(1); + } + data.put(c); + } + + public void add(final byte[] bytes) { + if (data.remaining() < bytes.length) { + enlarge(bytes.length); + } + data.put(bytes); + } + + public void add(final int... ints) { + if (data.remaining() < ints.length) { + enlarge(ints.length); + } + byte[] bytes = new byte[ints.length]; + for (int i = 0; i < ints.length; i++) { + bytes[i] = (byte) (i & 0xFF); + } + add(bytes); + } + + private void enlarge(final int size) { + final int length1 = 2 * data.limit(); + final int length2 = data.limit() + size; + ByteBuffer newData = ByteBuffer.allocate(Math.max(length1, length2)); + data.flip(); + newData.put(data); + data = newData; + } + + public int length() { + return data.limit() - data.remaining(); + } + + public byte[] toBytes() { + return data.array(); + } +} diff --git a/src/test/java/nl/sander/beejava/ConstantTeeTests.java b/src/test/java/nl/sander/beejava/ConstantTeeTests.java new file mode 100644 index 0000000..afbd4f9 --- /dev/null +++ b/src/test/java/nl/sander/beejava/ConstantTeeTests.java @@ -0,0 +1,114 @@ +package nl.sander.beejava; + +import nl.sander.beejava.constantpool.entry.*; +import nl.sander.beejava.flags.FieldAccessFlag; +import nl.sander.beejava.flags.MethodAccessFlag; +import org.junit.jupiter.api.Test; + +import java.util.Iterator; +import java.util.Set; + +import static nl.sander.beejava.CodeLine.line; +import static nl.sander.beejava.Opcode.*; +import static nl.sander.beejava.flags.ClassAccessFlag.PUBLIC; +import static org.junit.jupiter.api.Assertions.assertEquals; + +public class ConstantTeeTests { + @Test + public void testMethodRefEntryForSuperConstructor() { + BeeClass classWithIntField = createEmptyClass(); + Set constantTree = new ConstantTreeCreator().createConstantTree(classWithIntField); + assertEquals(1, constantTree.size()); + ConstantPoolEntry superConstructor = constantTree.iterator().next(); + + assertEquals(MethodRefEntry.class, superConstructor.getClass()); + MethodRefEntry methodRefEntry = (MethodRefEntry) superConstructor; + + Set methodRefEntryChildren = methodRefEntry.getChildren(); + assertEquals(2, methodRefEntryChildren.size()); + + Iterator firstChildren = methodRefEntryChildren.iterator(); + ConstantPoolEntry child1 = firstChildren.next(); + assertEquals(ClassEntry.class, child1.getClass()); + ClassEntry classEntry = (ClassEntry) child1; + + Set classEntryChildren = classEntry.getChildren(); + assertEquals(1, classEntryChildren.size()); + ConstantPoolEntry child2 = classEntryChildren.iterator().next(); + + assertEquals(Utf8Entry.class, child2.getClass()); + Utf8Entry className = (Utf8Entry) child2; + assertEquals("java/lang/Object", className.getUtf8()); + + ConstantPoolEntry child3 = firstChildren.next(); + assertEquals(NameAndTypeEntry.class, child3.getClass()); + NameAndTypeEntry nameAndTypeEntry = (NameAndTypeEntry) child3; + + Set nameAndTypeEntryChildren = nameAndTypeEntry.getChildren(); + assertEquals(2, nameAndTypeEntryChildren.size()); + Iterator nameAndTypeChildrenIterator = nameAndTypeEntryChildren.iterator(); + + ConstantPoolEntry child4 = nameAndTypeChildrenIterator.next(); + assertEquals(Utf8Entry.class, child4.getClass()); + Utf8Entry name = (Utf8Entry) child4; + assertEquals("", name.getUtf8()); + + ConstantPoolEntry child5 = nameAndTypeChildrenIterator.next(); + assertEquals(Utf8Entry.class, child5.getClass()); + Utf8Entry type = (Utf8Entry) child5; + assertEquals("()V", type.getUtf8()); + } + + private BeeClass createEmptyClass() { + BeeConstructor constructor = BeeConstructor.builder() + .withAccessFlags(MethodAccessFlag.PUBLIC) + .withCode( + line(0, LOAD, Ref.THIS), + line(1, INVOKE, Ref.SUPER, "", "()"), + line(5, RETURN)) + .build(); + + return BeeClass.builder() + .withClassFileVersion(Version.V14) + .withPackage("nl.sander.beejava.test") + .withAccessFlags(PUBLIC) + .withName("EmptyBean") + .withSuperClass(Object.class) + .withConstructors(constructor) + .build(); + } + + private BeeClass createClassWithIntField() { + BeeField intField = BeeField.builder() + .withAccessFlags(FieldAccessFlag.PRIVATE) + .withType(int.class) + .withName("intField") + .build(); + + BeeParameter intValueParameter = BeeParameter.create(int.class, "intValue"); + + BeeConstructor constructor = BeeConstructor.builder() + .withAccessFlags(MethodAccessFlag.PUBLIC) + .withFormalParameters(intValueParameter) + .withCode( + line(0, LOAD, Ref.THIS), + line(1, INVOKE, Ref.SUPER, "", "()"), + line(2, LOAD, Ref.THIS), + line(3, LOAD, intValueParameter), + line(4, PUT, intField), + line(5, RETURN)) + .build(); + + return BeeClass.builder() + .withClassFileVersion(Version.V14) + .withPackage("nl.sander.beejava.test") + .withAccessFlags(PUBLIC) + .withName("IntBean") + .withSuperClass(Object.class) + .withFields(intField) + .withConstructors(constructor) + .build(); + } + + +} diff --git a/src/test/java/nl/sander/beejava/testclasses/IntBean.java b/src/test/java/nl/sander/beejava/testclasses/IntBean.java new file mode 100644 index 0000000..4a1adb8 --- /dev/null +++ b/src/test/java/nl/sander/beejava/testclasses/IntBean.java @@ -0,0 +1,10 @@ +package nl.sander.beejava.testclasses; + +public class IntBean { +// private int intField; + +// public IntBean(int intField) { +// this.intField = intField; +// } + +}