diff --git a/src/main/java/io/github/bldl/astParsing/AstManipulator.java b/src/main/java/io/github/bldl/astParsing/AstManipulator.java index 7b6408e..4e40fbe 100644 --- a/src/main/java/io/github/bldl/astParsing/AstManipulator.java +++ b/src/main/java/io/github/bldl/astParsing/AstManipulator.java @@ -11,6 +11,10 @@ import com.github.javaparser.ast.type.TypeParameter; import com.github.javaparser.ast.visitor.ModifierVisitor; import com.github.javaparser.ast.visitor.Visitable; +import com.github.javaparser.symbolsolver.JavaSymbolSolver; +import com.github.javaparser.symbolsolver.resolution.typesolvers.CombinedTypeSolver; +import com.github.javaparser.symbolsolver.resolution.typesolvers.JavaParserTypeSolver; +import com.github.javaparser.symbolsolver.resolution.typesolvers.ReflectionTypeSolver; import com.github.javaparser.utils.CodeGenerationUtils; import com.github.javaparser.utils.SourceRoot; import io.github.bldl.annotationProcessing.annotations.MyVariance; @@ -46,9 +50,14 @@ public class AstManipulator { public AstManipulator(Messager messager, String sourceFolder) { this.messager = messager; this.sourceFolder = sourceFolder; + CombinedTypeSolver typeSolver = new CombinedTypeSolver(); + typeSolver.add(new ReflectionTypeSolver()); + typeSolver.add(new JavaParserTypeSolver(Paths.get("src/main/java"))); + JavaSymbolSolver symbolSolver = new JavaSymbolSolver(typeSolver); sourceRoot = new SourceRoot( CodeGenerationUtils.mavenModuleRoot(AstManipulator.class).resolve(sourceFolder)); - classHierarchy = computeClassHierarchy(); + sourceRoot.getParserConfiguration().setSymbolResolver(symbolSolver); + classHierarchy = (new ClassHierarchyComputer(sourceRoot, messager, sourceFolder)).computeClassHierarchy(); } public void applyChanges() { @@ -110,13 +119,6 @@ public Visitable visit(TypeParameter n, Void arg) { }, null); } - public ClassHierarchyGraph computeClassHierarchy() { - ClassHierarchyGraph g = new ClassHierarchyGraph<>(); - g.addVertex("Object"); - computeClassHierarchyRec(g, Paths.get(sourceFolder).toFile(), ""); - return g; - } - private ClassData computeClassData(String cls, String packageName, Map mp) { CompilationUnit cu = sourceRoot.parse(packageName, cls); Map indexAndBound = new HashMap<>(); @@ -151,9 +153,7 @@ private void changeAST(File dir, ClassData classData, Map me Set> varsToWatch = new HashSet<>(); cu.accept(new VariableCollector(classData), varsToWatch); cu.accept( - new SubtypingCheckVisitor(collectMethodParams(cu, classData), collectMethodTypes(cu), messager, - varsToWatch, classData, - classHierarchy), + new SubtypingCheckVisitor(collectMethodParams(cu, classData), messager, classData, classHierarchy), null); cu.accept(new TypeEraserVisitor(classData), null); for (Pair var : varsToWatch) { @@ -163,35 +163,7 @@ private void changeAST(File dir, ClassData classData, Map me } } - private void computeClassHierarchyRec(ClassHierarchyGraph g, File dir, String packageName) { - for (File file : dir.listFiles()) { - String fileName = file.getName(); - if (file.isDirectory()) { - if (!fileName.equals(OUTPUT_NAME)) - computeClassHierarchyRec(g, file, appendPackageDeclaration(packageName, fileName)); - continue; - } - if (!isJavaFile(file)) - continue; - - CompilationUnit cu = sourceRoot.parse(packageName, fileName); - - cu.findAll(ClassOrInterfaceDeclaration.class).forEach(cls -> { - NodeList supertypes = cls.getExtendedTypes(); - supertypes.addAll(cls.getImplementedTypes()); - g.addVertex(cls.getNameAsString()); - for (ClassOrInterfaceType supertype : supertypes) { - if (!g.containsVertex(supertype.getNameAsString())) - g.addVertex(supertype.getNameAsString()); - g.addEdge(supertype.getNameAsString(), cls.getNameAsString()); - } - if (supertypes.isEmpty()) - g.addEdge("Object", cls.getNameAsString()); - }); - } - } - - private boolean isJavaFile(File file) { + public static boolean isJavaFile(File file) { return file.getName().endsWith(".java"); } @@ -200,38 +172,27 @@ private void changePackageDeclaration(CompilationUnit cu) { cu.setPackageDeclaration(new PackageDeclaration(new Name(newPackageName))); } - private String appendPackageDeclaration(String existing, String toAppend) { + public static String appendPackageDeclaration(String existing, String toAppend) { if (existing.equals("")) return toAppend; return existing + "." + toAppend; } private Map> collectMethodParams(CompilationUnit cu, ClassData classData) { - Map> mp = new HashMap<>(); + Map> methodParams = new HashMap<>(); cu.findAll(MethodDeclaration.class).forEach(dec -> { + String methodName = dec.getNameAsString(); + if (methodParams.containsKey(methodName)) { + messager.printMessage(Kind.ERROR, "Duplicate methods inside a class. Can't handle polymorphism."); + return; + } + methodParams.put(methodName, new HashMap<>()); NodeList params = dec.getParameters(); for (int i = 0; i < params.size(); ++i) { - Parameter param = params.get(i); - if (!(param.getType() instanceof ClassOrInterfaceType)) - continue; - ClassOrInterfaceType type = ((ClassOrInterfaceType) param.getType()); - String methodName = dec.getNameAsString(); - if (type.getNameAsString().equals(classData.className())) { - mp.putIfAbsent(methodName, new HashMap<>()); - mp.get(methodName).put(i, - type); - } + Type type = params.get(i).getType(); + methodParams.get(methodName).put(i, type); } }); - return mp; - } - - private Map collectMethodTypes(CompilationUnit cu) { - Map mp = new HashMap<>(); - cu.findAll(MethodDeclaration.class).forEach(dec -> { - String methodName = dec.getNameAsString(); - mp.put(methodName, dec.getType()); - }); - return mp; + return methodParams; } } \ No newline at end of file diff --git a/src/main/java/io/github/bldl/astParsing/ClassHierarchyComputer.java b/src/main/java/io/github/bldl/astParsing/ClassHierarchyComputer.java new file mode 100644 index 0000000..1a0ed35 --- /dev/null +++ b/src/main/java/io/github/bldl/astParsing/ClassHierarchyComputer.java @@ -0,0 +1,85 @@ +package io.github.bldl.astParsing; + +import java.io.File; +import java.nio.file.Paths; + +import javax.annotation.processing.Messager; + +import com.github.javaparser.ast.CompilationUnit; +import com.github.javaparser.ast.NodeList; +import com.github.javaparser.ast.body.ClassOrInterfaceDeclaration; +import com.github.javaparser.ast.body.EnumDeclaration; +import com.github.javaparser.ast.body.RecordDeclaration; +import com.github.javaparser.ast.body.TypeDeclaration; +import com.github.javaparser.ast.type.ClassOrInterfaceType; +import com.github.javaparser.utils.SourceRoot; + +import io.github.bldl.graph.ClassHierarchyGraph; + +public class ClassHierarchyComputer { + + private SourceRoot sourceRoot; + private Messager messager; + private String sourceFolder; + + public ClassHierarchyComputer(SourceRoot sourceRoot, Messager messager, String sourceFolder) { + this.sourceRoot = sourceRoot; + this.messager = messager; + this.sourceFolder = sourceFolder; + } + + public ClassHierarchyGraph computeClassHierarchy() { + ClassHierarchyGraph g = new ClassHierarchyGraph<>(); + g.addVertex("Object"); + computeClassHierarchyRec(g, Paths.get(sourceFolder).toFile(), ""); + return g; + } + + private void computeClassHierarchyRec(ClassHierarchyGraph g, File dir, String packageName) { + for (File file : dir.listFiles()) { + String fileName = file.getName(); + if (file.isDirectory()) { + if (!fileName.equals(AstManipulator.OUTPUT_NAME)) + computeClassHierarchyRec(g, file, AstManipulator.appendPackageDeclaration(packageName, fileName)); + continue; + } + if (!AstManipulator.isJavaFile(file)) + continue; + + CompilationUnit cu = sourceRoot.parse(packageName, fileName); + + cu.findAll(RecordDeclaration.class).forEach(record -> { + NodeList supertypes = record.getImplementedTypes(); + handleDeclaration(packageName, g, supertypes, record); + }); + + cu.findAll(EnumDeclaration.class).forEach(enm -> { + NodeList supertypes = enm.getImplementedTypes(); + handleDeclaration(packageName, g, supertypes, enm); + }); + + cu.findAll(ClassOrInterfaceDeclaration.class).forEach(cls -> { + NodeList supertypes = cls.getExtendedTypes(); + supertypes.addAll(cls.getImplementedTypes()); + handleDeclaration(packageName, g, supertypes, cls); + }); + } + } + + private void handleDeclaration(String packageName, ClassHierarchyGraph g, + NodeList supertypes, + TypeDeclaration declaration) { + g.addVertex(getQualifiedName(packageName, declaration.getNameAsString())); + for (ClassOrInterfaceType supertype : supertypes) { + if (!g.containsVertex(supertype.getNameAsString())) + g.addVertex(supertype.getNameAsString()); + g.addEdge(supertype.getNameAsString(), declaration.getNameAsString()); + } + if (supertypes.isEmpty()) + g.addEdge("Object", declaration.getNameAsString()); + } + + private String getQualifiedName(String packageName, String typeName) { + return packageName + "." + typeName; + } +} diff --git a/src/main/java/io/github/bldl/astParsing/visitors/SubtypingCheckVisitor.java b/src/main/java/io/github/bldl/astParsing/visitors/SubtypingCheckVisitor.java index f33add9..c8d5ba3 100644 --- a/src/main/java/io/github/bldl/astParsing/visitors/SubtypingCheckVisitor.java +++ b/src/main/java/io/github/bldl/astParsing/visitors/SubtypingCheckVisitor.java @@ -2,72 +2,49 @@ import java.util.HashMap; import java.util.Map; -import java.util.Map.Entry; import java.util.Optional; -import java.util.Set; import javax.annotation.processing.Messager; import javax.tools.Diagnostic.Kind; import com.github.javaparser.ast.body.VariableDeclarator; import com.github.javaparser.ast.expr.AssignExpr; -import com.github.javaparser.ast.expr.BinaryExpr; -import com.github.javaparser.ast.expr.CastExpr; -import com.github.javaparser.ast.expr.ClassExpr; -import com.github.javaparser.ast.expr.EnclosedExpr; import com.github.javaparser.ast.expr.Expression; -import com.github.javaparser.ast.expr.FieldAccessExpr; -import com.github.javaparser.ast.expr.InstanceOfExpr; import com.github.javaparser.ast.expr.MethodCallExpr; -import com.github.javaparser.ast.expr.NameExpr; -import com.github.javaparser.ast.expr.ObjectCreationExpr; -import com.github.javaparser.ast.expr.UnaryExpr; import com.github.javaparser.ast.stmt.ForEachStmt; -import com.github.javaparser.ast.type.ClassOrInterfaceType; import com.github.javaparser.ast.type.Type; import com.github.javaparser.ast.visitor.VoidVisitorAdapter; +import com.github.javaparser.resolution.types.ResolvedReferenceType; +import com.github.javaparser.resolution.types.ResolvedType; import io.github.bldl.annotationProcessing.annotations.MyVariance; import io.github.bldl.astParsing.util.ClassData; -import io.github.bldl.astParsing.util.ParamData; import io.github.bldl.graph.ClassHierarchyGraph; -import io.github.bldl.util.Pair; public class SubtypingCheckVisitor extends VoidVisitorAdapter { private final Map> methodParams; - private Map methodTypes; private final Messager messager; - private final Map varsToWatchMap = new HashMap<>(); private final ClassData classData; private final ClassHierarchyGraph classHierarchy; - public SubtypingCheckVisitor(Map> methodParams, Map methodTypes, + public SubtypingCheckVisitor(Map> methodParams, Messager messager, - Set> varsToWatch, ClassData classData, + ClassData classData, ClassHierarchyGraph classHierarchy) { this.methodParams = methodParams; - this.methodTypes = methodTypes; this.messager = messager; this.classData = classData; this.classHierarchy = classHierarchy; - varsToWatch.forEach(p -> { - varsToWatchMap.put(p.first, p.second); - }); } public void visit(MethodCallExpr methodCall, Void arg) { super.visit(methodCall, arg); if (!methodParams.containsKey(methodCall.getNameAsString())) return; - for (Entry param : methodParams.get(methodCall.getNameAsString()).entrySet()) { - Expression e = methodCall.getArgument(param.getKey()); - ClassOrInterfaceType argumentType = resolveType(e); - if (argumentType == null) { - messager.printMessage(Kind.WARNING, "Cannot resolve type for expression: " + e.toString()); - continue; - } - boolean valid = isValidSubtype((ClassOrInterfaceType) param.getValue(), - argumentType, - classData.params()); + // ResolvedMethodDeclaration methodDeclaration = methodCall.resolve(); + for (int i = 0; i < methodCall.getArguments().size(); ++i) { + ResolvedType argumentType = methodCall.getArgument(i).calculateResolvedType(), + parameterType = methodParams.get(methodCall.getNameAsString()).get(i).resolve(); + boolean valid = isValidSubtype(parameterType, argumentType); if (!valid) messager.printMessage(Kind.ERROR, String.format("Invalid subtype for method call: %s", methodCall.toString())); @@ -76,13 +53,9 @@ public void visit(MethodCallExpr methodCall, Void arg) { public void visit(AssignExpr assignExpr, Void arg) { super.visit(assignExpr, arg); - ClassOrInterfaceType assignedType = resolveType(assignExpr.getValue()), - assigneeType = resolveType(assignExpr.getTarget()); - if (assignedType == null || assigneeType == null) { - messager.printMessage(Kind.WARNING, "Cannot resolve type for expression: " + assignExpr.toString()); - return; - } - boolean valid = isValidSubtype(assigneeType, assignedType, null); + ResolvedType assignedType = assignExpr.getValue().calculateResolvedType(), + assigneeType = assignExpr.getTarget().calculateResolvedType(); + boolean valid = isValidSubtype(assigneeType, assignedType); if (!valid) messager.printMessage(Kind.ERROR, String.format("Invalid subtype for assignment expression call: %s\n%s is not a subtype of %s", @@ -96,18 +69,13 @@ public void visit(ForEachStmt n, Void arg) { public void visit(VariableDeclarator declaration, Void arg) { super.visit(declaration, arg); - Type assigneeType = declaration.getType(); - if (!(assigneeType instanceof ClassOrInterfaceType)) - return; + ResolvedType assigneeType = declaration.getType().resolve(); Optional initializer = declaration.getInitializer(); if (initializer.isEmpty()) return; - ClassOrInterfaceType assignedType = resolveType(initializer.get()); - if (assignedType == null) - return; - boolean valid = isValidSubtype((ClassOrInterfaceType) assigneeType, - assignedType, - classData.params()); + ResolvedType assignedType = initializer.get().calculateResolvedType(); + boolean valid = isValidSubtype(assigneeType, + assignedType); if (!valid) messager.printMessage(Kind.ERROR, String.format("Invalid subtype for variable declaration: %s\n %s is not a subtype of %s", @@ -115,114 +83,75 @@ public void visit(VariableDeclarator declaration, Void arg) { assignedType.toString(), assigneeType.toString())); } - private ClassOrInterfaceType resolveType(Expression e) { - if (e instanceof EnclosedExpr) - return resolveType(((EnclosedExpr) e).getInner()); - if (e instanceof UnaryExpr) - return resolveType(((UnaryExpr) e).getExpression()); - if (e instanceof BinaryExpr) { - BinaryExpr binExp = (BinaryExpr) e; - ClassOrInterfaceType t1 = resolveType(binExp.getLeft()), t2 = resolveType(binExp.getRight()); - if (t1.asString().equals(t2.asString())) - return t1; - return null; - } - if (e instanceof MethodCallExpr) { - Type t = methodTypes.get(((MethodCallExpr) e).getNameAsString()); - return t instanceof ClassOrInterfaceType ? (ClassOrInterfaceType) t : null; - } - if (e instanceof NameExpr) - return varsToWatchMap.getOrDefault(((NameExpr) e).getNameAsString(), null); - if (e instanceof FieldAccessExpr) - return varsToWatchMap.getOrDefault(((FieldAccessExpr) e).getNameAsString(), null); - if (e instanceof CastExpr) { - Type t = ((CastExpr) e).getType(); - return t instanceof ClassOrInterfaceType ? (ClassOrInterfaceType) t : null; - } - if (e instanceof ObjectCreationExpr) - return ((ObjectCreationExpr) e).getType(); - if (e instanceof ClassExpr) { - Type t = ((ClassExpr) e).getType(); - return t instanceof ClassOrInterfaceType ? (ClassOrInterfaceType) t : null; - } - if (e instanceof InstanceOfExpr) - return new ClassOrInterfaceType(null, "Boolean"); - return null; - } + private boolean isValidSubtype(ResolvedType aType, ResolvedType assignType) { + if (!aType.isReferenceType() || !assignType.isReferenceType()) + return true; - private boolean isValidSubtype(ClassOrInterfaceType assigneeType, ClassOrInterfaceType assignedType, - Map params) { - if (!classHierarchy.containsVertex(assigneeType.getNameAsString())) { + ResolvedReferenceType assigneeType = aType.asReferenceType(), assignedType = assignType.asReferenceType(); + if (!classHierarchy.containsVertex(assigneeType.getQualifiedName())) { messager.printMessage(Kind.WARNING, String.format("%s is not a user defined type, so no subtyping checks can be made", assigneeType)); return true; } - if (!classHierarchy.containsVertex(assignedType.getNameAsString())) { + if (!classHierarchy.containsVertex(assignedType.getQualifiedName())) { messager.printMessage(Kind.WARNING, String.format("%s is not a user defined type, so no subtyping checks can be made", assignedType)); return true; } - if (!assigneeType.getTypeArguments().isPresent() || !assignedType.getTypeArguments().isPresent()) + if (!assigneeType.typeParametersValues().isEmpty() || !assignedType.typeParametersValues().isEmpty()) return true; - var assigneeArgs = assigneeType.getTypeArguments().get(); - var assignedArgs = assignedType.getTypeArguments().get(); + var assigneeArgs = assigneeType.typeParametersValues(); + var assignedArgs = assignedType.typeParametersValues(); boolean isSubtype = true; - - if (assignedArgs.size() == 0) - return true; // cannot perform type inference - - if (assigneeType.getNameAsString().equals(classData.className())) { - Map mp = new HashMap<>(); + // TODO update classData to use qualified name + if (assigneeType.getQualifiedName().equals(classData.className())) { + Map paramVariance = new HashMap<>(); for (var param : classData.params().values()) - mp.put(param.index(), param.variance()); + paramVariance.put(param.index(), param.variance()); for (int i = 0; i < assigneeArgs.size(); ++i) { - if (!(assignedArgs.get(i) instanceof ClassOrInterfaceType) - || !(assignedArgs.get(i) instanceof ClassOrInterfaceType)) { - continue; - } - if (!mp.containsKey(i)) { - isSubtype = isSubtype && isValidSubtype((ClassOrInterfaceType) assigneeArgs.get(i), - (ClassOrInterfaceType) assignedArgs.get(i), - params); + isSubtype = isSubtype && isValidSubtype(assigneeArgs.get(i), assignedArgs.get(i)); + ResolvedType assigneeArgType = assigneeArgs.get(i); + ResolvedType assignedArgType = assignedArgs.get(i); + if (!paramVariance.containsKey(i) || !assigneeArgType.isReferenceType() + || !assignedArgType.isReferenceType()) continue; - } - switch (mp.get(i).variance()) { + + switch (paramVariance.get(i).variance()) { case COVARIANT: - return classHierarchy.isDescendant( - ((ClassOrInterfaceType) assigneeArgs.get(i)).getNameAsString(), - ((ClassOrInterfaceType) assignedArgs.get(i)).getNameAsString(), mp.get(i).depth()); + isSubtype = isSubtype && classHierarchy.isDescendant( + assigneeArgType.asReferenceType().getQualifiedName(), + assignedArgType.asReferenceType().getQualifiedName(), paramVariance.get(i).depth()); + break; case CONTRAVARIANT: - return classHierarchy.isDescendant( - ((ClassOrInterfaceType) assignedArgs.get(i)).getNameAsString(), - ((ClassOrInterfaceType) assigneeArgs.get(i)).getNameAsString(), mp.get(i).depth()); + isSubtype = isSubtype && classHierarchy.isDescendant( + assignedArgType.asReferenceType().getQualifiedName(), + assigneeArgType.asReferenceType().getQualifiedName(), paramVariance.get(i).depth()); + break; case BIVARIANT: - return classHierarchy.isDescendant( - ((ClassOrInterfaceType) assigneeArgs.get(i)).getNameAsString(), - ((ClassOrInterfaceType) assignedArgs.get(i)).getNameAsString(), mp.get(i).depth()) + isSubtype = isSubtype && classHierarchy.isDescendant( + assigneeArgType.asReferenceType().getQualifiedName(), + assignedArgType.asReferenceType().getQualifiedName(), paramVariance.get(i).depth()) || classHierarchy.isDescendant( - ((ClassOrInterfaceType) assignedArgs.get(i)).getNameAsString(), - ((ClassOrInterfaceType) assigneeArgs.get(i)).getNameAsString(), - mp.get(i).depth()); + assignedArgType.asReferenceType().getQualifiedName(), + assigneeArgType.asReferenceType().getQualifiedName(), + paramVariance.get(i).depth()); + break; case SIDEVARIANT: - return classHierarchy.sameLevel(((ClassOrInterfaceType) assignedArgs.get(i)).getNameAsString(), - ((ClassOrInterfaceType) assigneeArgs.get(i)).getNameAsString()); + isSubtype = isSubtype && classHierarchy.sameLevel( + assignedArgType.asReferenceType().getQualifiedName(), + assigneeArgType.asReferenceType().getQualifiedName()); + break; default: - return false; + return true; } } + return isSubtype; } - for (int i = 0; i < assigneeArgs.size(); ++i) { - if (!(assignedArgs.get(i) instanceof ClassOrInterfaceType) - || !(assignedArgs.get(i) instanceof ClassOrInterfaceType)) { - continue; - } - isSubtype = isSubtype && isValidSubtype((ClassOrInterfaceType) assigneeArgs.get(i), - (ClassOrInterfaceType) assignedArgs.get(i), - params); - } - return classHierarchy.isDescendant(assigneeType.getNameAsString(), - assignedType.getNameAsString(), -1); + for (int i = 0; i < assigneeArgs.size(); ++i) + isSubtype = isSubtype && isValidSubtype(assigneeArgs.get(i), assignedArgs.get(i)); + return classHierarchy.isDescendant(assigneeType.getQualifiedName(), + assignedType.getQualifiedName(), -1); } }