From 7e6aeaf728d521c27c40b14e2800202f817170ff Mon Sep 17 00:00:00 2001 From: Daniel Holle Date: Fri, 7 Jun 2024 12:03:16 +0200 Subject: [PATCH] Make Function Types implement others to allow Subtyping, fixes #337 --- pom.xml | 4 +- resources/bytecode/javFiles/Bug337.jav | 10 ++++ .../dhbwstuttgart/bytecode/FunNGenerator.java | 42 ++++++++++--- .../de/dhbwstuttgart/core/JavaTXCompiler.java | 1 + .../target/generate/ASTToTargetAST.java | 60 +++++++++++++++---- src/test/java/TestComplete.java | 8 +++ src/test/java/targetast/TestCodegen.java | 4 ++ 7 files changed, 110 insertions(+), 19 deletions(-) create mode 100644 resources/bytecode/javFiles/Bug337.jav diff --git a/pom.xml b/pom.xml index 120f4204..d9dbc114 100644 --- a/pom.xml +++ b/pom.xml @@ -54,8 +54,8 @@ http://maven.apache.org/maven-v4_0_0.xsd"> 3.11.0 --enable-preview - 21 - 21 + 22 + 22 diff --git a/resources/bytecode/javFiles/Bug337.jav b/resources/bytecode/javFiles/Bug337.jav new file mode 100644 index 00000000..92bb6fa8 --- /dev/null +++ b/resources/bytecode/javFiles/Bug337.jav @@ -0,0 +1,10 @@ +import java.lang.Integer; +import java.lang.Number; +import java.lang.Object; + +public class Bug337 { + public void main() { + Fun1$$ fun1 = x -> x.hashCode() + 1; + Fun1$$ fun2 = fun1; + } +} \ No newline at end of file diff --git a/src/main/java/de/dhbwstuttgart/bytecode/FunNGenerator.java b/src/main/java/de/dhbwstuttgart/bytecode/FunNGenerator.java index 411d9bf2..819f5a3e 100644 --- a/src/main/java/de/dhbwstuttgart/bytecode/FunNGenerator.java +++ b/src/main/java/de/dhbwstuttgart/bytecode/FunNGenerator.java @@ -33,6 +33,23 @@ public class FunNGenerator { public static class GenericParameters { int start; public List parameters = new ArrayList<>(); + final String descriptor; + final List inParams; + + public GenericParameters(List params) { + this.inParams = params; + var type = new TargetRefType(FunNGenerator.getSuperClassName(params.size() - 1), params); + descriptor = applyDescriptor(type, this); + System.out.println(this.parameters); + } + + public TargetType getReturnType() { + return FunNGenerator.getReturnType(inParams); + } + + public List getArguments() { + return FunNGenerator.getArguments(inParams); + } } private static String applyDescriptor(TargetType type, GenericParameters gep) { @@ -98,12 +115,11 @@ public class FunNGenerator { return String.format("Fun%d$$", numberArguments); } - public static byte[] generateSpecializedBytecode(List argumentTypes, TargetType returnType, GenericParameters gep) { - List parameters = Stream - .concat(argumentTypes.stream(), Stream.of(returnType)) - .toList(); + public static byte[] generateSpecializedBytecode(GenericParameters gep, List superInterfaces) { + var argumentTypes = gep.getArguments(); + var returnType = gep.getReturnType(); - StringBuilder funNClassSignature = new StringBuilder(objectSignature + applyDescriptor(new TargetRefType(getSuperClassName(argumentTypes.size()), parameters), gep)); + StringBuilder funNClassSignature = new StringBuilder(objectSignature + gep.descriptor); boolean containsGeneric = false; String genericSignature = "<"; @@ -114,10 +130,18 @@ public class FunNGenerator { genericSignature += ">"; if (containsGeneric) funNClassSignature.insert(0, genericSignature); - System.out.println(funNClassSignature.toString()); + + for (var superInterface : superInterfaces) { + funNClassSignature.append('L'); + funNClassSignature.append(superInterface); + funNClassSignature.append(';'); + } + + var interfaces = new ArrayList<>(superInterfaces); + interfaces.add(getSuperClassName(argumentTypes.size())); ClassWriter classWriter = new ClassWriter(0); - classWriter.visit(bytecodeVersion, ACC_PUBLIC | ACC_ABSTRACT | ACC_INTERFACE, getSpecializedClassName(argumentTypes, returnType), funNClassSignature.toString(), objectSuperType, new String[]{getSuperClassName(argumentTypes.size())}); + classWriter.visit(bytecodeVersion, ACC_PUBLIC | ACC_ABSTRACT | ACC_INTERFACE, getSpecializedClassName(argumentTypes, returnType), funNClassSignature.toString(), objectSuperType, interfaces.toArray(String[]::new)); classWriter.visitEnd(); return classWriter.toByteArray(); } @@ -133,6 +157,10 @@ public class FunNGenerator { } } + public static String getSpecializedClassName(GenericParameters gep) { + return getSpecializedClassName(getArguments(gep.inParams), getReturnType(gep.inParams)); + } + public static String getSpecializedClassName(List argumentTypes, TargetType returnType) { return String.format("Fun%d$$%s%s", argumentTypes.size(), diff --git a/src/main/java/de/dhbwstuttgart/core/JavaTXCompiler.java b/src/main/java/de/dhbwstuttgart/core/JavaTXCompiler.java index 62d69b16..ed11515c 100644 --- a/src/main/java/de/dhbwstuttgart/core/JavaTXCompiler.java +++ b/src/main/java/de/dhbwstuttgart/core/JavaTXCompiler.java @@ -783,6 +783,7 @@ public class JavaTXCompiler { }); } generatedGenerics.put(sf, converter.javaGenerics()); + converter.generateFunNTypes(); return generatedClasses; } diff --git a/src/main/java/de/dhbwstuttgart/target/generate/ASTToTargetAST.java b/src/main/java/de/dhbwstuttgart/target/generate/ASTToTargetAST.java index 1c485d7e..3908af06 100644 --- a/src/main/java/de/dhbwstuttgart/target/generate/ASTToTargetAST.java +++ b/src/main/java/de/dhbwstuttgart/target/generate/ASTToTargetAST.java @@ -479,6 +479,55 @@ public class ASTToTargetAST { return TargetFunNType.fromParams(params, filteredParams); } + private boolean isSubtype(TargetType test, TargetType other) { + var testClass = compiler.getClass(new JavaClassName(test.name())); + var otherClass = compiler.getClass(new JavaClassName(other.name())); + if (testClass == null) return false; + while (testClass != null) { + if (testClass.equals(otherClass)) return true; + if (testClass.getClassName().equals(new JavaClassName("java.lang.Object"))) break; + testClass = compiler.getClass(testClass.getSuperClass().getName()); + } + return false; + } + + private boolean isSupertype(TargetType test, TargetType other) { + return isSubtype(other, test); + } + + private boolean isSubtype(FunNGenerator.GenericParameters test, FunNGenerator.GenericParameters other) { + if (test.getArguments().size() != other.getArguments().size()) return false; + if (!isSubtype(test.getReturnType(), other.getReturnType())) return false; + for (int i = 0; i < test.getArguments().size(); i++) { + var arg1 = test.getArguments().get(i); + var arg2 = other.getArguments().get(i); + if (!isSupertype(arg1, arg2)) return false; + } + return true; + } + + public void generateFunNTypes() { + for (var entry : usedFunN.entrySet()) { + var gep = entry.getValue(); + var superInterfaces = usedFunN.values().stream() + .filter(g -> !g.equals(gep)) + .filter(genericParameters -> isSubtype(gep, genericParameters)) + .map(FunNGenerator::getSpecializedClassName) + .toList(); + + var code = FunNGenerator.generateSpecializedBytecode(gep, superInterfaces); + + try { + classLoader.findClass(entry.getKey()); + } catch (ClassNotFoundException e) { + try { + classLoader.loadClass(code); + } catch (LinkageError ignored) {} + } + auxiliaries.put(entry.getKey(), code); + } + } + protected TargetType convert(RefTypeOrTPHOrWildcardOrGeneric input, GenerateGenerics generics) { return input.acceptTV(new TypeVisitor<>() { @Override @@ -513,17 +562,8 @@ public class ASTToTargetAST { } FunNGenerator.GenericParameters gep = null; if (!usedFunN.containsKey(className)) { - gep = new FunNGenerator.GenericParameters(); - var code = FunNGenerator.generateSpecializedBytecode(FunNGenerator.getArguments(params), FunNGenerator.getReturnType(params), gep); - try { - classLoader.findClass(className); - } catch (ClassNotFoundException e) { - try { - classLoader.loadClass(code); - } catch (LinkageError ignored) {} - } + gep = new FunNGenerator.GenericParameters(params); usedFunN.put(className, gep); - auxiliaries.put(className, code); } else { gep = usedFunN.get(className); } diff --git a/src/test/java/TestComplete.java b/src/test/java/TestComplete.java index 87c6f2aa..2b95681e 100644 --- a/src/test/java/TestComplete.java +++ b/src/test/java/TestComplete.java @@ -1131,4 +1131,12 @@ public class TestComplete { var clazz = classFiles.get("Bug333"); var instance = clazz.getDeclaredConstructor().newInstance(); } + + @Test + public void testBug337() throws Exception { + var classFiles = generateClassFiles(new ByteArrayClassLoader(), "Bug337.jav"); + var clazz = classFiles.get("Bug337"); + var instance = clazz.getDeclaredConstructor().newInstance(); + clazz.getDeclaredMethod("main").invoke(instance); + } } diff --git a/src/test/java/targetast/TestCodegen.java b/src/test/java/targetast/TestCodegen.java index 3d698169..fb901776 100644 --- a/src/test/java/targetast/TestCodegen.java +++ b/src/test/java/targetast/TestCodegen.java @@ -60,6 +60,8 @@ public class TestCodegen { } }).collect(Collectors.toMap(Class::getName, Function.identity()))); + converter.generateFunNTypes(); + for (var entry : converter.auxiliaries.entrySet()) { writeClassFile(entry.getKey(), entry.getValue()); } @@ -104,6 +106,8 @@ public class TestCodegen { } }).collect(Collectors.toMap(Class::getName, Function.identity())); + converter.generateFunNTypes(); + for (var entry : converter.auxiliaries.entrySet()) { writeClassFile(entry.getKey(), entry.getValue()); }