Implement records

This commit is contained in:
Daniel Holle 2023-07-31 15:11:35 +02:00
parent 4f3164a48a
commit b0f7a264c2
15 changed files with 207 additions and 61 deletions

View File

@ -0,0 +1,22 @@
import java.lang.Integer;
record Rec(Integer a, Integer b) {}
/*public class Rec {
x; y;
Rec(Integer a, Integer b) {
x = a;
y = b;
}
}*/
public class RecordTest {
a = new Rec(10, 20);
b = new Rec(10, 20);
c = new Rec(20, 40);
doesEqual() { return a.equals(b); }
doesNotEqual() { return b.equals(c); }
hashCode() { return a.hashCode(); }
toString() { return a.toString(); }
}

View File

@ -1,18 +1,12 @@
package de.dhbwstuttgart.bytecode;
import de.dhbwstuttgart.exceptions.NotImplementedException;
import de.dhbwstuttgart.syntaxtree.statement.Break;
import de.dhbwstuttgart.target.tree.*;
import de.dhbwstuttgart.target.tree.expression.*;
import de.dhbwstuttgart.target.tree.type.*;
import org.antlr.v4.codegen.Target;
import org.objectweb.asm.*;
import java.lang.invoke.CallSite;
import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.lang.invoke.MethodType;
import java.sql.Array;
import java.lang.invoke.*;
import java.util.*;
import static org.objectweb.asm.Opcodes.*;
@ -20,13 +14,13 @@ import static de.dhbwstuttgart.target.tree.expression.TargetBinaryOp.*;
import static de.dhbwstuttgart.target.tree.expression.TargetLiteral.*;
public class Codegen {
private final TargetClass clazz;
private final TargetStructure clazz;
private final ClassWriter cw;
public final String className;
private int lambdaCounter = 0;
private final HashMap<TargetLambdaExpression, TargetMethod> lambdas = new HashMap<>();
public Codegen(TargetClass clazz) {
public Codegen(TargetStructure clazz) {
this.clazz = clazz;
this.className = clazz.qualifiedName();
this.cw = new ClassWriter(ClassWriter.COMPUTE_FRAMES | ClassWriter.COMPUTE_MAXS);
@ -1148,10 +1142,13 @@ public class Codegen {
if (cse.labels().size() == 1) {
var label = cse.labels().get(0);
if (label instanceof TargetSwitch.Guard gd)
bindLabel(state, tmp, aSwitch.expr().type(), gd.inner());
else if (label instanceof TargetSwitch.Pattern pat)
bindLabel(state, tmp, aSwitch.expr().type(), pat);
if (label instanceof TargetSwitch.Guard gd){
state.mv.visitVarInsn(ALOAD, tmp);
bindPattern(state, aSwitch.expr().type(), gd.inner());
} else if (label instanceof TargetSwitch.Pattern pat) {
state.mv.visitVarInsn(ALOAD, tmp);
bindPattern(state, aSwitch.expr().type(), pat);
}
if (label instanceof TargetSwitch.Guard gd) {
generate(state, gd.expression());
@ -1191,13 +1188,13 @@ public class Codegen {
state.exitScope();
}
private void bindLabel(State state, int tmp, TargetType type, TargetSwitch.Pattern pat) {
private void bindPattern(State state, TargetType type, TargetSwitch.Pattern pat) {
if (pat instanceof TargetSwitch.SimplePattern sp) {
state.mv.visitVarInsn(ALOAD, tmp);
var local = state.createVariable(sp.name(), sp.type());
convertTo(state, type, local.type);
boxPrimitive(state, local.type);
state.mv.visitVarInsn(ASTORE, local.index);
} else if (pat instanceof TargetSwitch.ComplexPattern cp) {
convertTo(state, type, cp.type());
}
}
@ -1218,7 +1215,11 @@ public class Codegen {
}
private void generateField(TargetField field) {
cw.visitField(field.access() | ACC_PUBLIC, field.name(), field.type().toSignature(), field.type().toDescriptor(), null);
var access = field.access();
if ((access & ACC_PRIVATE) == 0 && (access & ACC_PROTECTED) == 0) // TODO Implement access modifiers properly
access |= ACC_PUBLIC;
cw.visitField(access, field.name(), field.type().toSignature(), field.type().toDescriptor(), null);
}
private void generateConstructor(TargetConstructor constructor) {
@ -1265,7 +1266,7 @@ public class Codegen {
mv.visitEnd();
}
private static String generateSignature(TargetClass clazz, Set<TargetGeneric> generics) {
private static String generateSignature(TargetStructure clazz, Set<TargetGeneric> generics) {
String ret = "";
if (generics.size() > 0) {
ret += "<";
@ -1280,7 +1281,11 @@ public class Codegen {
}
public byte[] generate() {
cw.visit(V1_8, clazz.modifiers() | ACC_PUBLIC | ACC_SUPER, clazz.qualifiedName(), generateSignature(clazz, clazz.generics()), clazz.superType() != null ? clazz.superType().getInternalName() : "java/lang/Object", clazz.implementingInterfaces().stream().map(TargetType::toSignature).toArray(String[]::new));
var access = clazz.modifiers();
if ((access & ACC_PRIVATE) == 0 && (access & ACC_PROTECTED) == 0) // TODO Implement access modifiers properly
access |= ACC_PUBLIC;
cw.visit(V1_8, access | ACC_SUPER, clazz.qualifiedName(), generateSignature(clazz, clazz.generics()), clazz.superType() != null ? clazz.superType().getInternalName() : "java/lang/Object", clazz.implementingInterfaces().stream().map(TargetType::toSignature).toArray(String[]::new));
if (clazz.txGenerics() != null)
cw.visitAttribute(new JavaTXSignatureAttribute(generateSignature(clazz, clazz.txGenerics())));
@ -1288,7 +1293,53 @@ public class Codegen {
clazz.constructors().forEach(this::generateConstructor);
clazz.methods().forEach(this::generateMethod);
if (clazz instanceof TargetRecord)
generateRecordMethods();
cw.visitEnd();
return cw.toByteArray();
}
private void generateRecordMethods() {
var mt = MethodType.methodType(Object.class, MethodHandles.Lookup.class, String.class, TypeDescriptor.class, Class.class, String.class, MethodHandle[].class);
var bootstrap = new Handle(H_INVOKESTATIC, "java/lang/runtime/ObjectMethods", "bootstrap", mt.toMethodDescriptorString(), false);
var bootstrapArgs = new Object[2 + clazz.fields().size()];
bootstrapArgs[0] = Type.getObjectType(clazz.getName());
bootstrapArgs[1] = String.join(";", clazz.fields().stream().map(TargetField::name).toArray(String[]::new));
for (var i = 0; i < clazz.fields().size(); i++) {
var field = clazz.fields().get(i);
var fieldRef = new Handle(H_GETFIELD, clazz.getName(), field.name(), field.type().toDescriptor(), false);
bootstrapArgs[i + 2] = fieldRef;
}
{ // hashCode
var mv = cw.visitMethod(ACC_PUBLIC, "hashCode", "()I", null, null);
mv.visitCode();
mv.visitVarInsn(ALOAD, 0);
mv.visitInvokeDynamicInsn("hashCode", "(L" + clazz.getName() + ";)I", bootstrap, bootstrapArgs);
mv.visitInsn(IRETURN);
mv.visitMaxs(0, 0);
mv.visitEnd();
}
{ // equals
var mv = cw.visitMethod(ACC_PUBLIC, "equals", "(Ljava/lang/Object;)Z", null, null);
mv.visitCode();
mv.visitVarInsn(ALOAD, 0);
mv.visitVarInsn(ALOAD, 1);
mv.visitInvokeDynamicInsn("equals", "(L" + clazz.getName() + ";Ljava/lang/Object;)Z", bootstrap, bootstrapArgs);
mv.visitInsn(IRETURN);
mv.visitMaxs(0, 0);
mv.visitEnd();
}
{ // toString
var mv = cw.visitMethod(ACC_PUBLIC, "toString", "()Ljava/lang/String;", null, null);
mv.visitCode();
mv.visitVarInsn(ALOAD, 0);
mv.visitInvokeDynamicInsn("toString", "(L" + clazz.getName() + ";)Ljava/lang/String;", bootstrap, bootstrapArgs);
mv.visitInsn(ARETURN);
mv.visitMaxs(0, 0);
mv.visitEnd();
}
}
}

View File

@ -135,22 +135,23 @@ public class JavaTXCompiler {
void addMethods(SourceFile sf, ClassOrInterface cl, List<ClassOrInterface> importedClasses, ClassOrInterface objectClass) {
if (!cl.areMethodsAdded()) {
ClassOrInterface superclass = null;
if (cl.getSuperClass().getName().equals(new JavaClassName("java.lang.Object"))) {
superclass = objectClass;
Optional<ClassOrInterface> optSuperclass = importedClasses.stream().filter(x -> x.getClassName().equals(cl.getSuperClass().getName())).findFirst();
if (optSuperclass.isPresent()) {
superclass = optSuperclass.get();
} else {
Optional<ClassOrInterface> optSuperclass = importedClasses.stream().filter(x -> x.getClassName().equals(cl.getSuperClass().getName())).findFirst();
optSuperclass = sf.KlassenVektor.stream().filter(x -> x.getClassName().equals(cl.getSuperClass().getName())).findFirst();
if (optSuperclass.isPresent()) {
superclass = optSuperclass.get();
addMethods(sf, superclass, importedClasses, objectClass);
} else {
optSuperclass = sf.KlassenVektor.stream().filter(x -> x.getClassName().equals(cl.getSuperClass().getName())).findFirst();
if (optSuperclass.isPresent()) {
superclass = optSuperclass.get();
addMethods(sf, superclass, importedClasses, objectClass);
} else {
// throw new ClassNotFoundException("");
}
try {
var className = cl.getSuperClass().getName().toString();
superclass = ASTFactory.createClass(classLoader.loadClass(className));
} catch (ClassNotFoundException ignored) {}
// throw new ClassNotFoundException("");
}
}
Iterator<RefTypeOrTPHOrWildcardOrGeneric> paraIt = cl.getSuperClass().getParaList().iterator();
Iterator<GenericTypeVar> tvarVarIt = superclass.getGenerics().iterator();

View File

@ -87,6 +87,8 @@ import de.dhbwstuttgart.syntaxtree.type.Void;
import de.dhbwstuttgart.typeinference.constraints.GenericsResolver;
import javassist.compiler.SyntaxError;
import javax.swing.text.html.Option;
public class SyntaxTreeGenerator {
private JavaClassRegistry reg;
private final GenericsRegistry globalGenerics;
@ -248,7 +250,7 @@ public class SyntaxTreeGenerator {
} else {
genericClassParameters = TypeGenerator.convert(recordDeclaration.genericDeclarationList(), name, "", reg, generics);
}
RefType superClass = new RefType(ASTFactory.createObjectClass().getClassName(), offset);
RefType superClass = new RefType(ASTFactory.createClass(java.lang.Record.class).getClassName(), offset);
List<Field> fielddecl = new ArrayList<>();
List<Method> methods = new ArrayList<>();
List<Constructor> constructors = new ArrayList<>();

View File

@ -33,7 +33,11 @@ import org.objectweb.asm.signature.SignatureVisitor;
*/
public class ASTFactory {
private static final HashMap<java.lang.Class, ClassOrInterface> cache = new HashMap<>();
public static ClassOrInterface createClass(java.lang.Class jreClass) {
if (cache.containsKey(jreClass))
return cache.get(jreClass);
// TODO Inner classes
@ -141,7 +145,9 @@ public class ASTFactory {
Token offset = new NullToken(); // Braucht keinen Offset, da diese Klasse nicht aus einem Quellcode geparst wurde
return new ClassOrInterface(modifier, name, felder, Optional.empty() /* eingefuegt PL 2018-11-24 */, methoden, konstruktoren, genericDeclarationList, superClass, isInterface, implementedInterfaces, permittedSubtypes, offset);
var cinf = new ClassOrInterface(modifier, name, felder, Optional.empty() /* eingefuegt PL 2018-11-24 */, methoden, konstruktoren, genericDeclarationList, superClass, isInterface, implementedInterfaces, permittedSubtypes, offset);
cache.put(jreClass, cinf);
return cinf;
}
private static Field createField(java.lang.reflect.Field field, JavaClassName jreClass) {

View File

@ -4,6 +4,7 @@ import de.dhbwstuttgart.bytecode.FunNGenerator;
import de.dhbwstuttgart.environment.ByteArrayClassLoader;
import de.dhbwstuttgart.environment.IByteArrayClassLoader;
import de.dhbwstuttgart.syntaxtree.*;
import de.dhbwstuttgart.syntaxtree.Record;
import de.dhbwstuttgart.syntaxtree.factory.ASTFactory;
import de.dhbwstuttgart.syntaxtree.statement.*;
import de.dhbwstuttgart.syntaxtree.type.*;
@ -104,7 +105,7 @@ public class ASTToTargetAST {
return ret;
}
public TargetClass convert(ClassOrInterface input) {
public TargetStructure convert(ClassOrInterface input) {
currentClass = input;
Set<TargetGeneric> javaGenerics = new HashSet<>();
Set<TargetGeneric> txGenerics = new HashSet<>();
@ -132,7 +133,14 @@ public class ASTToTargetAST {
fieldInitializer = convert(input.getfieldInitializations().get().block);
TargetBlock finalFieldInitializer = fieldInitializer;
return new TargetClass(input.getModifiers(), input.getClassName().toString(), convert(input.getSuperClass(), generics.javaGenerics), javaGenerics, txGenerics, input.getSuperInterfaces().stream().map(clazz -> convert(clazz, generics.javaGenerics)).toList(), input.getConstructors().stream().map(constructor -> this.convert(constructor, finalFieldInitializer)).flatMap(List::stream).toList(), input.getFieldDecl().stream().map(this::convert).toList(), input.getMethods().stream().map(this::convert).flatMap(List::stream).toList());
var superInterfaces = input.getSuperInterfaces().stream().map(clazz -> convert(clazz, generics.javaGenerics)).toList();
var constructors = input.getConstructors().stream().map(constructor -> this.convert(constructor, finalFieldInitializer)).flatMap(List::stream).toList();
var fields = input.getFieldDecl().stream().map(this::convert).toList();
var methods = input.getMethods().stream().map(this::convert).flatMap(List::stream).toList();
if (input instanceof Record)
return new TargetRecord(input.getModifiers(), input.getClassName().toString(), javaGenerics, txGenerics, superInterfaces, constructors, fields, methods);
else return new TargetClass(input.getModifiers(), input.getClassName().toString(), convert(input.getSuperClass(), generics.javaGenerics), javaGenerics, txGenerics, superInterfaces, constructors, fields, methods);
}
private List<MethodParameter> convert(ParameterList input, GenerateGenerics generics) {

View File

@ -170,7 +170,7 @@ public class StatementToTargetExpression implements StatementVisitor {
}
Method findMethod(JavaClassName className, String name, List<TargetType> args) {
if (converter.sourceFile != null && converter.sourceFile.imports.contains(className)) {
if (converter.sourceFile != null /*&& converter.sourceFile.imports.contains(className)*/) {
try {
var clazz = converter.classLoader.loadClass(className.toString());
@ -374,6 +374,7 @@ public class StatementToTargetExpression implements StatementVisitor {
public void visit(RecordPattern aRecordPattern) {
result = new TargetSwitch.ComplexPattern(
converter.convert(aRecordPattern.getType()),
aRecordPattern.getName(),
aRecordPattern.getSubPattern().stream().map(x -> (TargetSwitch.Pattern) converter.convert(x)).toList()
);
}

View File

@ -11,7 +11,7 @@ import java.util.List;
import java.util.Set;
public record TargetClass(int modifiers, String qualifiedName, TargetType superType, Set<TargetGeneric> generics, Set<TargetGeneric> txGenerics, List<TargetType> implementingInterfaces,
List<TargetConstructor> constructors, List<TargetField> fields, List<TargetMethod> methods) {
List<TargetConstructor> constructors, List<TargetField> fields, List<TargetMethod> methods) implements TargetStructure {
public TargetClass(int modifiers, String qualifiedName) {
this(modifiers, qualifiedName, TargetType.Object, new HashSet<>(), new HashSet<>(), new ArrayList<>(), new ArrayList<>(), new ArrayList<>(), new ArrayList<>());
@ -20,26 +20,6 @@ public record TargetClass(int modifiers, String qualifiedName, TargetType superT
this(modifiers, qualifiedName, TargetType.Object, new HashSet<>(), new HashSet<>(), implementingInterfaces, new ArrayList<>(), new ArrayList<>(), new ArrayList<>());
}
public String getName() {
return qualifiedName.replaceAll("\\.", "/");
}
public void addMethod(int access, String name, Set<TargetGeneric> generics, List<MethodParameter> parameterTypes, TargetType returnType, TargetBlock block) {
this.methods.add(new TargetMethod(access, name, block, new TargetMethod.Signature(generics, parameterTypes, returnType), null));
}
public void addMethod(int access, String name, List<MethodParameter> parameterTypes, TargetType returnType, TargetBlock block) {
addMethod(access, name, Set.of(), parameterTypes, returnType, block);
}
public void addConstructor(int access, Set<TargetGeneric> generics, List<MethodParameter> paramterTypes, TargetBlock block) {
this.constructors.add(new TargetConstructor(access, generics, Set.of(), paramterTypes, List.of(), block, null));
}
public void addConstructor(int access, List<MethodParameter> paramterTypes, TargetBlock block) {
addConstructor(access, Set.of(), paramterTypes, block);
}
public void addField(int access, TargetRefType type, String name) {
this.fields.add(new TargetField(access, type, name));
}

View File

@ -0,0 +1,18 @@
package de.dhbwstuttgart.target.tree;
import de.dhbwstuttgart.target.tree.type.TargetRefType;
import de.dhbwstuttgart.target.tree.type.TargetType;
import java.util.Collections;
import java.util.List;
import java.util.Set;
public record TargetRecord(int modifiers, String qualifiedName, Set<TargetGeneric> generics, Set<TargetGeneric> txGenerics, List<TargetType> implementingInterfaces, List<TargetConstructor> constructors, List<TargetField> fields, List<TargetMethod> methods) implements TargetStructure {
public static final TargetType RECORD = new TargetRefType("java.lang.Record");
@Override
public TargetType superType() {
return RECORD;
}
}

View File

@ -0,0 +1,40 @@
package de.dhbwstuttgart.target.tree;
import de.dhbwstuttgart.target.tree.expression.TargetBlock;
import de.dhbwstuttgart.target.tree.type.TargetType;
import java.util.List;
import java.util.Set;
public interface TargetStructure {
int modifiers();
String qualifiedName();
TargetType superType();
Set<TargetGeneric> generics();
Set<TargetGeneric> txGenerics();
List<TargetType> implementingInterfaces();
List<TargetConstructor> constructors();
List<TargetField> fields();
List<TargetMethod> methods();
default String getName() {
return qualifiedName().replaceAll("\\.", "/");
}
// These methods are only meant to be used for test cases, a Class record should be immutable!
default void addMethod(int access, String name, Set<TargetGeneric> generics, List<MethodParameter> parameterTypes, TargetType returnType, TargetBlock block) {
this.methods().add(new TargetMethod(access, name, block, new TargetMethod.Signature(generics, parameterTypes, returnType), null));
}
default void addMethod(int access, String name, List<MethodParameter> parameterTypes, TargetType returnType, TargetBlock block) {
addMethod(access, name, Set.of(), parameterTypes, returnType, block);
}
default void addConstructor(int access, Set<TargetGeneric> generics, List<MethodParameter> paramterTypes, TargetBlock block) {
this.constructors().add(new TargetConstructor(access, generics, Set.of(), paramterTypes, List.of(), block, null));
}
default void addConstructor(int access, List<MethodParameter> paramterTypes, TargetBlock block) {
addConstructor(access, Set.of(), paramterTypes, block);
}
}

View File

@ -27,10 +27,14 @@ public record TargetSwitch(TargetExpression expr, List<Case> cases, Case default
}
}
public sealed interface Pattern extends TargetExpression {}
public sealed interface Pattern extends TargetExpression {
default String name() {
return null;
}
}
public record SimplePattern(TargetType type, String name) implements Pattern {}
public record ComplexPattern(TargetType type, List<Pattern> subPatterns) implements Pattern {}
public record ComplexPattern(TargetType type, String name, List<Pattern> subPatterns) implements Pattern {}
public record Guard(Pattern inner, TargetExpression expression) implements Pattern {}
}

View File

@ -638,4 +638,15 @@ public class TestComplete {
var classFiles = generateClassFiles(new ByteArrayClassLoader(), "OL.jav");
var instance = classFiles.get("OL").getDeclaredConstructor().newInstance();
}
@Test
public void recordTest() throws Exception {
var classFiles = generateClassFiles(new ByteArrayClassLoader(), "RecordTest.jav");
var clazz = classFiles.get("RecordTest");
var instance = clazz.getDeclaredConstructor().newInstance();
assertTrue((Boolean) clazz.getDeclaredMethod("doesEqual").invoke(instance));
assertFalse((Boolean) clazz.getDeclaredMethod("doesNotEqual").invoke(instance));
System.out.println(clazz.getDeclaredMethod("hashCode").invoke(instance));
System.out.println(clazz.getDeclaredMethod("toString").invoke(instance));
}
}

View File

@ -62,10 +62,10 @@ public class TestNewFeatures {
resultingAST = resultingAST.replaceAll("TPH [A-Z]+", "TPH");
System.out.println("Expected:\n" + new String(expectedAST));
System.out.println("Result:\n" + new String(resultingAST));
assertEquals("Comparing expected and resulting AST for Record.jav", expectedAST, resultingAST);
assertEquals("Comparing expected and resulting AST for RecordTest.jav", expectedAST, resultingAST);
} catch (Exception exc) {
exc.printStackTrace();
fail("An error occured while generating the AST for Record.jav");
fail("An error occured while generating the AST for RecordTest.jav");
}
}

View File

@ -9,6 +9,7 @@ import de.dhbwstuttgart.syntaxtree.*;
import de.dhbwstuttgart.syntaxtree.type.RefType;
import de.dhbwstuttgart.target.generate.ASTToTargetAST;
import de.dhbwstuttgart.target.tree.TargetClass;
import de.dhbwstuttgart.target.tree.TargetStructure;
import de.dhbwstuttgart.typeinference.result.ResultSet;
import org.junit.Ignore;
import org.junit.Test;
@ -27,7 +28,7 @@ public class ASTToTypedTargetAST {
public void emptyClass() {
ClassOrInterface emptyClass = new ClassOrInterface(0, new JavaClassName("EmptyClass"), new ArrayList<>(), java.util.Optional.empty(), new ArrayList<>(), new ArrayList<>(), new GenericDeclarationList(new ArrayList<>(), new NullToken()), new RefType(new JavaClassName("Object"), new NullToken()), false, new ArrayList<>(), new ArrayList<>(), new NullToken());
ResultSet emptyResultSet = new ResultSet(new HashSet<>());
TargetClass emptyTargetClass = new ASTToTargetAST(List.of(emptyResultSet)).convert(emptyClass);
TargetStructure emptyTargetClass = new ASTToTargetAST(List.of(emptyResultSet)).convert(emptyClass);
assert emptyTargetClass.getName().equals("EmptyClass");
assert emptyTargetClass.methods().size() == 0;
assert emptyTargetClass.fields().size() == 0;

View File

@ -7,6 +7,7 @@ import de.dhbwstuttgart.environment.IByteArrayClassLoader;
import de.dhbwstuttgart.target.generate.ASTToTargetAST;
import de.dhbwstuttgart.target.tree.MethodParameter;
import de.dhbwstuttgart.target.tree.TargetClass;
import de.dhbwstuttgart.target.tree.TargetStructure;
import de.dhbwstuttgart.target.tree.expression.*;
import de.dhbwstuttgart.target.tree.type.TargetFunNType;
import de.dhbwstuttgart.target.tree.type.TargetRefType;
@ -66,7 +67,7 @@ public class TestCodegen {
return result;
}
public static Class<?> generateClass(TargetClass clazz, IByteArrayClassLoader classLoader) throws IOException {
public static Class<?> generateClass(TargetStructure clazz, IByteArrayClassLoader classLoader) throws IOException {
var codegen = new Codegen(clazz);
var code = codegen.generate();
writeClassFile(clazz.qualifiedName(), code);