From 0ed1c465a371f9aff16cb945b0d91c2cadd43880 Mon Sep 17 00:00:00 2001 From: unknown Date: Sat, 3 Feb 2024 19:46:46 -0500 Subject: [PATCH 1/5] add codes and tests --- .../specimin/PrunerVisitor.java | 27 ++++++++++++- .../specimin/SpeciminRunner.java | 5 ++- .../specimin/TargetMethodFinderVisitor.java | 40 +++++++++++++++++++ .../specimin/UnsolvedSymbolVisitor.java | 31 ++++++++++++++ .../specimin/InterfaceImplementedTest.java | 18 +++++++++ .../InterfaceWithGenericTypeTest.java | 18 +++++++++ .../InterfaceWithUnsolvedSymbols.java | 18 +++++++++ .../specimin/UnsolvedInterfaceTest.java | 18 +++++++++ .../specimin/UnusedInterfaceTest.java | 18 +++++++++ .../expected/com/example/Baz.java | 7 ++++ .../expected/com/example/Foo.java | 9 +++++ .../input/com/example/Baz.java | 9 +++++ .../input/com/example/Foo.java | 10 +++++ .../expected/com/example/Baz.java | 10 +++++ .../expected/com/example/Foo.java | 9 +++++ .../input/com/example/Baz.java | 13 ++++++ .../input/com/example/Foo.java | 9 +++++ .../expected/com/example/Baz.java | 8 ++++ .../expected/com/example/Foo.java | 9 +++++ .../input/com/example/Baz.java | 13 ++++++ .../input/com/example/Foo.java | 9 +++++ .../expected/com/example/Foo.java | 8 ++++ .../input/com/example/Foo.java | 10 +++++ .../expected/com/example/Foo.java | 8 ++++ .../input/com/example/Baz.java | 9 +++++ .../input/com/example/Foo.java | 9 +++++ 26 files changed, 350 insertions(+), 2 deletions(-) create mode 100644 src/test/java/org/checkerframework/specimin/InterfaceImplementedTest.java create mode 100644 src/test/java/org/checkerframework/specimin/InterfaceWithGenericTypeTest.java create mode 100644 src/test/java/org/checkerframework/specimin/InterfaceWithUnsolvedSymbols.java create mode 100644 src/test/java/org/checkerframework/specimin/UnsolvedInterfaceTest.java create mode 100644 src/test/java/org/checkerframework/specimin/UnusedInterfaceTest.java create mode 100644 src/test/resources/interfaceimplemented/expected/com/example/Baz.java create mode 100644 src/test/resources/interfaceimplemented/expected/com/example/Foo.java create mode 100644 src/test/resources/interfaceimplemented/input/com/example/Baz.java create mode 100644 src/test/resources/interfaceimplemented/input/com/example/Foo.java create mode 100644 src/test/resources/interfacewithgenerictype/expected/com/example/Baz.java create mode 100644 src/test/resources/interfacewithgenerictype/expected/com/example/Foo.java create mode 100644 src/test/resources/interfacewithgenerictype/input/com/example/Baz.java create mode 100644 src/test/resources/interfacewithgenerictype/input/com/example/Foo.java create mode 100644 src/test/resources/interfacewithunsolvedsymbols/expected/com/example/Baz.java create mode 100644 src/test/resources/interfacewithunsolvedsymbols/expected/com/example/Foo.java create mode 100644 src/test/resources/interfacewithunsolvedsymbols/input/com/example/Baz.java create mode 100644 src/test/resources/interfacewithunsolvedsymbols/input/com/example/Foo.java create mode 100644 src/test/resources/unsolvedinterface/expected/com/example/Foo.java create mode 100644 src/test/resources/unsolvedinterface/input/com/example/Foo.java create mode 100644 src/test/resources/unusedinterface/expected/com/example/Foo.java create mode 100644 src/test/resources/unusedinterface/input/com/example/Baz.java create mode 100644 src/test/resources/unusedinterface/input/com/example/Foo.java diff --git a/src/main/java/org/checkerframework/specimin/PrunerVisitor.java b/src/main/java/org/checkerframework/specimin/PrunerVisitor.java index 00582d5d..36995b25 100644 --- a/src/main/java/org/checkerframework/specimin/PrunerVisitor.java +++ b/src/main/java/org/checkerframework/specimin/PrunerVisitor.java @@ -3,6 +3,7 @@ import com.github.javaparser.StaticJavaParser; import com.github.javaparser.ast.ImportDeclaration; import com.github.javaparser.ast.Node; +import com.github.javaparser.ast.NodeList; import com.github.javaparser.ast.body.ClassOrInterfaceDeclaration; import com.github.javaparser.ast.body.ConstructorDeclaration; import com.github.javaparser.ast.body.FieldDeclaration; @@ -16,6 +17,7 @@ import com.github.javaparser.ast.expr.IntegerLiteralExpr; import com.github.javaparser.ast.expr.LongLiteralExpr; import com.github.javaparser.ast.expr.NullLiteralExpr; +import com.github.javaparser.ast.type.ClassOrInterfaceType; import com.github.javaparser.ast.type.PrimitiveType; import com.github.javaparser.ast.type.Type; import com.github.javaparser.ast.visitor.ModifierVisitor; @@ -55,6 +57,9 @@ public class PrunerVisitor extends ModifierVisitor { */ private Set classesUsedByTargetMethods; + /** This is to check whether the current compilation unit is a class or an interface. */ + private boolean isInsideAnInterface = false; + /** * This boolean tracks whether the element currently being visited is inside a target method. It * is set by {@link #visit(MethodDeclaration, Void)}. @@ -101,6 +106,24 @@ public Visitable visit(ClassOrInterfaceDeclaration decl, Void p) { decl.remove(); return decl; } + if (decl.isInterface()) { + this.isInsideAnInterface = true; + } else { + NodeList implementedInterfaces = decl.getImplementedTypes(); + Iterator iterator = implementedInterfaces.iterator(); + while (iterator.hasNext()) { + ClassOrInterfaceType interfaceType = iterator.next(); + try { + String typeFullName = interfaceType.resolve().getQualifiedName(); + if (!classesUsedByTargetMethods.contains(typeFullName)) { + iterator.remove(); + } + } catch (UnsolvedSymbolException e) { + iterator.remove(); + } + } + decl.setImplementedTypes(implementedInterfaces); + } return super.visit(decl, p); } @@ -129,7 +152,9 @@ public Visitable visit(MethodDeclaration methodDecl, Void p) { insideTargetMethod = false; return result; } else if (membersToEmpty.contains(resolved.getQualifiedSignature())) { - methodDecl.setBody(StaticJavaParser.parseBlock("{ throw new Error(); }")); + if (!isInsideAnInterface) { + methodDecl.setBody(StaticJavaParser.parseBlock("{ throw new Error(); }")); + } return methodDecl; } else { // if insideTargetMethod is true, this current method declaration belongs to an anonnymous diff --git a/src/main/java/org/checkerframework/specimin/SpeciminRunner.java b/src/main/java/org/checkerframework/specimin/SpeciminRunner.java index 897ea434..6579dbd4 100644 --- a/src/main/java/org/checkerframework/specimin/SpeciminRunner.java +++ b/src/main/java/org/checkerframework/specimin/SpeciminRunner.java @@ -142,6 +142,9 @@ public static void performMinimization( for (String targetFile : targetFiles) { parsedTargetFiles.put(targetFile, parseJavaFile(root, targetFile)); } + for (String targetFile : addMissingClass.getAddedTargetFiles()) { + parsedTargetFiles.put(targetFile, parseJavaFile(root, targetFile)); + } } for (CompilationUnit cu : parsedTargetFiles.values()) { @@ -249,7 +252,7 @@ public static void performMinimization( System.out.println("with error: " + e); } } - // delete all the temporary files created by UnsolvedSymbolVisitor + // delete all the temporary files created by UnsolvedSymbolVisitor deleteFiles(createdClass); } diff --git a/src/main/java/org/checkerframework/specimin/TargetMethodFinderVisitor.java b/src/main/java/org/checkerframework/specimin/TargetMethodFinderVisitor.java index 5832bec0..00d12bf9 100644 --- a/src/main/java/org/checkerframework/specimin/TargetMethodFinderVisitor.java +++ b/src/main/java/org/checkerframework/specimin/TargetMethodFinderVisitor.java @@ -95,6 +95,13 @@ public class TargetMethodFinderVisitor extends ModifierVisitor { */ private final Map importedClassToPackage; + /** + * This map connects the declaration of a method to the qualified name of the interface that + * contains it, if any. + */ + private final Map + methodDeclarationToInterfaceType = new HashMap<>(); + /** * Create a new target method finding visitor. * @@ -149,6 +156,13 @@ public Set getTargetMethods() { return targetMethods; } + private void updateMethodDeclarationToInterfaceType( + List methodList, ClassOrInterfaceType interfaceType) { + for (ResolvedMethodDeclaration method : methodList) { + this.methodDeclarationToInterfaceType.put(method, interfaceType); + } + } + @Override public Node visit(ImportDeclaration decl, Void p) { String classFullName = decl.getNameAsString(); @@ -163,6 +177,15 @@ public Node visit(ImportDeclaration decl, Void p) { @Override public Visitable visit(ClassOrInterfaceDeclaration decl, Void p) { + for (ClassOrInterfaceType interfaceType : decl.getImplementedTypes()) { + try { + updateMethodDeclarationToInterfaceType( + interfaceType.resolve().getAllMethods(), interfaceType); + } catch (UnsolvedSymbolException e) { + continue; + } + } + if (decl.isNestedType()) { this.classFQName += "." + decl.getName().toString(); } else { @@ -226,6 +249,7 @@ public Visitable visit(MethodDeclaration method, Void p) { } if (this.targetMethodNames.contains(methodName)) { + updateUsedClassesForInterface(method); updateUsedClassWithQualifiedClassName( method.resolve().getPackageName() + "." + method.resolve().getClassName()); insideTargetMethod = true; @@ -430,6 +454,22 @@ public Visitable visit(NameExpr expr, Void p) { return super.visit(expr, p); } + public void updateUsedClassesForInterface(MethodDeclaration method) { + for (ResolvedMethodDeclaration interfaceMethod : methodDeclarationToInterfaceType.keySet()) { + if (method.getNameAsString().equals(interfaceMethod.getName())) { + String methodReturnType = method.resolve().getReturnType().describe(); + String interfaceMethodReturnType = interfaceMethod.getReturnType().describe(); + if (methodReturnType.equals(interfaceMethodReturnType)) { + if (method.getParameters().size() == interfaceMethod.getNumberOfParams()) { + usedClass.add( + methodDeclarationToInterfaceType.get(interfaceMethod).resolve().getQualifiedName()); + usedMembers.add(interfaceMethod.getQualifiedSignature()); + } + } + } + } + } + /** * Given a method declaration, this method return the declaration of that method without the * return type. diff --git a/src/main/java/org/checkerframework/specimin/UnsolvedSymbolVisitor.java b/src/main/java/org/checkerframework/specimin/UnsolvedSymbolVisitor.java index fb799542..c16418b9 100644 --- a/src/main/java/org/checkerframework/specimin/UnsolvedSymbolVisitor.java +++ b/src/main/java/org/checkerframework/specimin/UnsolvedSymbolVisitor.java @@ -172,6 +172,9 @@ public class UnsolvedSymbolVisitor extends ModifierVisitor { */ private final Map staticImportedMembersMap = new HashMap<>(); + /** New files that should be added to the list of target files for the next iteration. */ + private final Set addedTargetFiles = new HashSet<>(); + /** * Create a new UnsolvedSymbolVisitor instance * @@ -281,6 +284,10 @@ public void setExceptionToFalse() { gotException = false; } + public Set getAddedTargetFiles() { + return addedTargetFiles; + } + @Override public Node visit(ImportDeclaration decl, Void arg) { if (decl.isAsterisk()) { @@ -319,6 +326,30 @@ public Visitable visit(ClassOrInterfaceDeclaration node, Void arg) { addTypeVariableScope(node.getTypeParameters()); Visitable result = super.visit(node, arg); typeVariables.removeFirst(); + + NodeList interfaceList = node.getImplementedTypes(); + for (ClassOrInterfaceType interfaceType : interfaceList) { + String qualifiedName = + classAndPackageMap.getOrDefault(className, this.currentPackage) + + "." + + interfaceType.getName().asString(); + if (classfileIsInOriginalCodebase(qualifiedName)) { + // add the source codes of the interface to the list of target files so that + // UnsolvedSymbolVisitor can solve symbols for that interface if needed. + String filePath = qualifiedName.replace(".", "/"); + if (filePath.contains("<")) { + filePath = filePath.substring(filePath.indexOf("<")); + } + filePath = filePath + ".java"; + if (!addedTargetFiles.contains(filePath)) { + // strictly speaking, there is no exception here. But we set gotException to true so that + // UnsolvedSymbolVisitor will run at least one more iteration to visit the newly added + // file. + this.gotException = true; + } + addedTargetFiles.add(filePath); + } + } return result; } diff --git a/src/test/java/org/checkerframework/specimin/InterfaceImplementedTest.java b/src/test/java/org/checkerframework/specimin/InterfaceImplementedTest.java new file mode 100644 index 00000000..1e7e31db --- /dev/null +++ b/src/test/java/org/checkerframework/specimin/InterfaceImplementedTest.java @@ -0,0 +1,18 @@ +package org.checkerframework.specimin; + +import org.junit.Test; + +import java.io.IOException; + +/** + * This test checks if Specimin can properly remove unused method signatures from an interface. + */ +public class InterfaceImplementedTest { + @Test + public void runTest() throws IOException { + SpeciminTestExecutor.runTestWithoutJarPaths( + "unsolvedinterface", + new String[] {"com/example/Foo.java"}, + new String[] {"com.example.Foo#doSomething()"}); + } +} diff --git a/src/test/java/org/checkerframework/specimin/InterfaceWithGenericTypeTest.java b/src/test/java/org/checkerframework/specimin/InterfaceWithGenericTypeTest.java new file mode 100644 index 00000000..bbd392ec --- /dev/null +++ b/src/test/java/org/checkerframework/specimin/InterfaceWithGenericTypeTest.java @@ -0,0 +1,18 @@ +package org.checkerframework.specimin; + +import org.junit.Test; + +import java.io.IOException; + +/** + * This test checks if Specimin can work for interfaces with generic types. + */ +public class InterfaceWithGenericTypeTest { + @Test + public void runTest() throws IOException { + SpeciminTestExecutor.runTestWithoutJarPaths( + "interfacewithgenerictype", + new String[] {"com/example/Foo.java"}, + new String[] {"com.example.Foo#doSomething()"}); + } +} diff --git a/src/test/java/org/checkerframework/specimin/InterfaceWithUnsolvedSymbols.java b/src/test/java/org/checkerframework/specimin/InterfaceWithUnsolvedSymbols.java new file mode 100644 index 00000000..96f89adb --- /dev/null +++ b/src/test/java/org/checkerframework/specimin/InterfaceWithUnsolvedSymbols.java @@ -0,0 +1,18 @@ +package org.checkerframework.specimin; + +import org.junit.Test; + +import java.io.IOException; + +/** + * This test makes sure that Specimin will not crash if an interface contains unsolved symbols. + */ +public class InterfaceWithUnsolvedSymbols { + @Test + public void runTest() throws IOException { + SpeciminTestExecutor.runTestWithoutJarPaths( + "interfacewithunsolvedsymbols", + new String[] {"com/example/Foo.java"}, + new String[] {"com.example.Foo#doSomething()"}); + } +} diff --git a/src/test/java/org/checkerframework/specimin/UnsolvedInterfaceTest.java b/src/test/java/org/checkerframework/specimin/UnsolvedInterfaceTest.java new file mode 100644 index 00000000..8aeef7c8 --- /dev/null +++ b/src/test/java/org/checkerframework/specimin/UnsolvedInterfaceTest.java @@ -0,0 +1,18 @@ +package org.checkerframework.specimin; + +import org.junit.Test; + +import java.io.IOException; + +/** + * This test checks if Specimin can properly remove an unsolved interface. + */ +public class UnsolvedInterfaceTest { + @Test + public void runTest() throws IOException { + SpeciminTestExecutor.runTestWithoutJarPaths( + "unsolvedinterface", + new String[] {"com/example/Foo.java"}, + new String[] {"com.example.Foo#doManyThing()"}); + } +} diff --git a/src/test/java/org/checkerframework/specimin/UnusedInterfaceTest.java b/src/test/java/org/checkerframework/specimin/UnusedInterfaceTest.java new file mode 100644 index 00000000..bb761166 --- /dev/null +++ b/src/test/java/org/checkerframework/specimin/UnusedInterfaceTest.java @@ -0,0 +1,18 @@ +package org.checkerframework.specimin; + +import org.junit.Test; + +import java.io.IOException; + +/** + * This test checks if Specimin can properly remove unused interfaces. + */ +public class UnusedInterfaceTest { + @Test + public void runTest() throws IOException { + SpeciminTestExecutor.runTestWithoutJarPaths( + "unusedinterface", + new String[] {"com/example/Foo.java"}, + new String[] {"com.example.Foo#doManyThing()"}); + } +} diff --git a/src/test/resources/interfaceimplemented/expected/com/example/Baz.java b/src/test/resources/interfaceimplemented/expected/com/example/Baz.java new file mode 100644 index 00000000..0c362cdf --- /dev/null +++ b/src/test/resources/interfaceimplemented/expected/com/example/Baz.java @@ -0,0 +1,7 @@ +package com.example; + +// Baz.java +public interface Baz { + + void doSomething(); +} diff --git a/src/test/resources/interfaceimplemented/expected/com/example/Foo.java b/src/test/resources/interfaceimplemented/expected/com/example/Foo.java new file mode 100644 index 00000000..c94cbedc --- /dev/null +++ b/src/test/resources/interfaceimplemented/expected/com/example/Foo.java @@ -0,0 +1,9 @@ +package com.example; + +class Foo implements Baz { + + @Override + public void doSomething() { + System.out.println("Foo is doing something!"); + } +} diff --git a/src/test/resources/interfaceimplemented/input/com/example/Baz.java b/src/test/resources/interfaceimplemented/input/com/example/Baz.java new file mode 100644 index 00000000..5a2aed0f --- /dev/null +++ b/src/test/resources/interfaceimplemented/input/com/example/Baz.java @@ -0,0 +1,9 @@ +package com.example; + +// Baz.java +public interface Baz { + void doSomething(); + void doOneThing(); + void doNothing(); +} + diff --git a/src/test/resources/interfaceimplemented/input/com/example/Foo.java b/src/test/resources/interfaceimplemented/input/com/example/Foo.java new file mode 100644 index 00000000..a27a1c1f --- /dev/null +++ b/src/test/resources/interfaceimplemented/input/com/example/Foo.java @@ -0,0 +1,10 @@ +package com.example; + +// Foo.java +class Foo implements Baz { + @Override + public void doSomething() { + System.out.println("Foo is doing something!"); + } + +} diff --git a/src/test/resources/interfacewithgenerictype/expected/com/example/Baz.java b/src/test/resources/interfacewithgenerictype/expected/com/example/Baz.java new file mode 100644 index 00000000..60d55ec9 --- /dev/null +++ b/src/test/resources/interfacewithgenerictype/expected/com/example/Baz.java @@ -0,0 +1,10 @@ +package com.example; + +// Baz.java +public interface Baz { + + void doSomething(T value); + + // sadly we can't remove this method. + void doSomething(int x); +} diff --git a/src/test/resources/interfacewithgenerictype/expected/com/example/Foo.java b/src/test/resources/interfacewithgenerictype/expected/com/example/Foo.java new file mode 100644 index 00000000..0517895e --- /dev/null +++ b/src/test/resources/interfacewithgenerictype/expected/com/example/Foo.java @@ -0,0 +1,9 @@ +package com.example; + +class Foo implements Baz { + + @Override + public void doSomething(String value) { + System.out.println("Foo is doing something with: " + value); + } +} diff --git a/src/test/resources/interfacewithgenerictype/input/com/example/Baz.java b/src/test/resources/interfacewithgenerictype/input/com/example/Baz.java new file mode 100644 index 00000000..fedfe554 --- /dev/null +++ b/src/test/resources/interfacewithgenerictype/input/com/example/Baz.java @@ -0,0 +1,13 @@ +package com.example; + +// Baz.java +public interface Baz { + void doSomething(T value); + // this method will be removed. + void doSomething(); + // sadly we can't remove this method. + void doSomething(int x); + +} + + diff --git a/src/test/resources/interfacewithgenerictype/input/com/example/Foo.java b/src/test/resources/interfacewithgenerictype/input/com/example/Foo.java new file mode 100644 index 00000000..f12b1bc9 --- /dev/null +++ b/src/test/resources/interfacewithgenerictype/input/com/example/Foo.java @@ -0,0 +1,9 @@ +package com.example; + +class Foo implements Baz { + @Override + public void doSomething(String value) { + System.out.println("Foo is doing something with: " + value); + } + +} diff --git a/src/test/resources/interfacewithunsolvedsymbols/expected/com/example/Baz.java b/src/test/resources/interfacewithunsolvedsymbols/expected/com/example/Baz.java new file mode 100644 index 00000000..4805dba8 --- /dev/null +++ b/src/test/resources/interfacewithunsolvedsymbols/expected/com/example/Baz.java @@ -0,0 +1,8 @@ +package com.example; + +public interface Baz { + + void doSomething(T value); + + void doSomething(int x); +} diff --git a/src/test/resources/interfacewithunsolvedsymbols/expected/com/example/Foo.java b/src/test/resources/interfacewithunsolvedsymbols/expected/com/example/Foo.java new file mode 100644 index 00000000..0517895e --- /dev/null +++ b/src/test/resources/interfacewithunsolvedsymbols/expected/com/example/Foo.java @@ -0,0 +1,9 @@ +package com.example; + +class Foo implements Baz { + + @Override + public void doSomething(String value) { + System.out.println("Foo is doing something with: " + value); + } +} diff --git a/src/test/resources/interfacewithunsolvedsymbols/input/com/example/Baz.java b/src/test/resources/interfacewithunsolvedsymbols/input/com/example/Baz.java new file mode 100644 index 00000000..8ff34c81 --- /dev/null +++ b/src/test/resources/interfacewithunsolvedsymbols/input/com/example/Baz.java @@ -0,0 +1,13 @@ +package com.example; + +import org.testing.UnsolvedType; + +public interface Baz { + void doSomething(T value); + // this method will be removed. + UnsolvedType doSomething(); + void doSomething(int x); + +} + + diff --git a/src/test/resources/interfacewithunsolvedsymbols/input/com/example/Foo.java b/src/test/resources/interfacewithunsolvedsymbols/input/com/example/Foo.java new file mode 100644 index 00000000..f12b1bc9 --- /dev/null +++ b/src/test/resources/interfacewithunsolvedsymbols/input/com/example/Foo.java @@ -0,0 +1,9 @@ +package com.example; + +class Foo implements Baz { + @Override + public void doSomething(String value) { + System.out.println("Foo is doing something with: " + value); + } + +} diff --git a/src/test/resources/unsolvedinterface/expected/com/example/Foo.java b/src/test/resources/unsolvedinterface/expected/com/example/Foo.java new file mode 100644 index 00000000..78b81681 --- /dev/null +++ b/src/test/resources/unsolvedinterface/expected/com/example/Foo.java @@ -0,0 +1,8 @@ +package com.example; + +class Foo { + + public void doManyThing() { + System.out.println("Foo is doing many things!"); + } +} diff --git a/src/test/resources/unsolvedinterface/input/com/example/Foo.java b/src/test/resources/unsolvedinterface/input/com/example/Foo.java new file mode 100644 index 00000000..06c41506 --- /dev/null +++ b/src/test/resources/unsolvedinterface/input/com/example/Foo.java @@ -0,0 +1,10 @@ +package com.example; + +import org.testing.Baz; + +class Foo implements Baz { + public void doManyThing() { + System.out.println("Foo is doing many things!"); + } + +} diff --git a/src/test/resources/unusedinterface/expected/com/example/Foo.java b/src/test/resources/unusedinterface/expected/com/example/Foo.java new file mode 100644 index 00000000..78b81681 --- /dev/null +++ b/src/test/resources/unusedinterface/expected/com/example/Foo.java @@ -0,0 +1,8 @@ +package com.example; + +class Foo { + + public void doManyThing() { + System.out.println("Foo is doing many things!"); + } +} diff --git a/src/test/resources/unusedinterface/input/com/example/Baz.java b/src/test/resources/unusedinterface/input/com/example/Baz.java new file mode 100644 index 00000000..5a2aed0f --- /dev/null +++ b/src/test/resources/unusedinterface/input/com/example/Baz.java @@ -0,0 +1,9 @@ +package com.example; + +// Baz.java +public interface Baz { + void doSomething(); + void doOneThing(); + void doNothing(); +} + diff --git a/src/test/resources/unusedinterface/input/com/example/Foo.java b/src/test/resources/unusedinterface/input/com/example/Foo.java new file mode 100644 index 00000000..225fc0fd --- /dev/null +++ b/src/test/resources/unusedinterface/input/com/example/Foo.java @@ -0,0 +1,9 @@ +package com.example; + +// Foo.java +class Foo implements Baz { + public void doManyThing() { + System.out.println("Foo is doing many things!"); + } + +} From 1ccab61da9f50446a4a3d566ae51e49b36827a2e Mon Sep 17 00:00:00 2001 From: unknown Date: Sat, 3 Feb 2024 20:01:07 -0500 Subject: [PATCH 2/5] add Javadoc and do some cleaning --- .../specimin/SpeciminRunner.java | 2 +- .../specimin/TargetMethodFinderVisitor.java | 35 +++++++++++++------ .../specimin/UnsolvedSymbolVisitor.java | 5 +++ 3 files changed, 31 insertions(+), 11 deletions(-) diff --git a/src/main/java/org/checkerframework/specimin/SpeciminRunner.java b/src/main/java/org/checkerframework/specimin/SpeciminRunner.java index 6579dbd4..911c39aa 100644 --- a/src/main/java/org/checkerframework/specimin/SpeciminRunner.java +++ b/src/main/java/org/checkerframework/specimin/SpeciminRunner.java @@ -252,7 +252,7 @@ public static void performMinimization( System.out.println("with error: " + e); } } - // delete all the temporary files created by UnsolvedSymbolVisitor + // delete all the temporary files created by UnsolvedSymbolVisitor deleteFiles(createdClass); } diff --git a/src/main/java/org/checkerframework/specimin/TargetMethodFinderVisitor.java b/src/main/java/org/checkerframework/specimin/TargetMethodFinderVisitor.java index 00d12bf9..696bfec7 100644 --- a/src/main/java/org/checkerframework/specimin/TargetMethodFinderVisitor.java +++ b/src/main/java/org/checkerframework/specimin/TargetMethodFinderVisitor.java @@ -96,8 +96,8 @@ public class TargetMethodFinderVisitor extends ModifierVisitor { private final Map importedClassToPackage; /** - * This map connects the declaration of a method to the qualified name of the interface that - * contains it, if any. + * This map connects the resolved declaration of a method to the interface that contains it, if + * any. */ private final Map methodDeclarationToInterfaceType = new HashMap<>(); @@ -156,6 +156,13 @@ public Set getTargetMethods() { return targetMethods; } + /** + * Updates the mapping of method declarations to their corresponding interface type based on a + * list of methods and the interface type that contains those methods. + * + * @param methodList the list of resolved method declarations + * @param interfaceType the interface containing the specified methods. + */ private void updateMethodDeclarationToInterfaceType( List methodList, ClassOrInterfaceType interfaceType) { for (ResolvedMethodDeclaration method : methodList) { @@ -249,13 +256,13 @@ public Visitable visit(MethodDeclaration method, Void p) { } if (this.targetMethodNames.contains(methodName)) { - updateUsedClassesForInterface(method); + ResolvedMethodDeclaration resolvedMethod = method.resolve(); + updateUsedClassesForInterface(resolvedMethod); updateUsedClassWithQualifiedClassName( - method.resolve().getPackageName() + "." + method.resolve().getClassName()); + method.resolve().getPackageName() + "." + resolvedMethod.getClassName()); insideTargetMethod = true; - targetMethods.add(method.resolve().getQualifiedSignature()); + targetMethods.add(resolvedMethod.getQualifiedSignature()); unfoundMethods.remove(methodName); - ResolvedMethodDeclaration resolvedMethod = method.resolve(); usedClass.add(resolvedMethod.getPackageName() + "." + resolvedMethod.getClassName()); Type returnType = method.getType(); // JavaParser may misinterpret unresolved array types as reference types. @@ -454,13 +461,21 @@ public Visitable visit(NameExpr expr, Void p) { return super.visit(expr, p); } - public void updateUsedClassesForInterface(MethodDeclaration method) { + /** + * Updates the list of used classes based on a resolved method declaration. If the input method + * originates from an interface, that interface will be added to the list of used classes. The + * determination of whether a method belongs to an interface is based on three criteria: method + * name, method return type, and the number of parameters. + * + * @param method The resolved method declaration to be used for updating the list. + */ + public void updateUsedClassesForInterface(ResolvedMethodDeclaration method) { for (ResolvedMethodDeclaration interfaceMethod : methodDeclarationToInterfaceType.keySet()) { - if (method.getNameAsString().equals(interfaceMethod.getName())) { - String methodReturnType = method.resolve().getReturnType().describe(); + if (method.getName().equals(interfaceMethod.getName())) { + String methodReturnType = method.getReturnType().describe(); String interfaceMethodReturnType = interfaceMethod.getReturnType().describe(); if (methodReturnType.equals(interfaceMethodReturnType)) { - if (method.getParameters().size() == interfaceMethod.getNumberOfParams()) { + if (method.getNumberOfParams() == interfaceMethod.getNumberOfParams()) { usedClass.add( methodDeclarationToInterfaceType.get(interfaceMethod).resolve().getQualifiedName()); usedMembers.add(interfaceMethod.getQualifiedSignature()); diff --git a/src/main/java/org/checkerframework/specimin/UnsolvedSymbolVisitor.java b/src/main/java/org/checkerframework/specimin/UnsolvedSymbolVisitor.java index c16418b9..b7de1e70 100644 --- a/src/main/java/org/checkerframework/specimin/UnsolvedSymbolVisitor.java +++ b/src/main/java/org/checkerframework/specimin/UnsolvedSymbolVisitor.java @@ -284,6 +284,11 @@ public void setExceptionToFalse() { gotException = false; } + /** + * Get the set of target files that should be added for the next iteration. + * + * @return the value of addedTargetFiles. + */ public Set getAddedTargetFiles() { return addedTargetFiles; } From b2a461b5b4838216bfe618ccc5b889fce9870d21 Mon Sep 17 00:00:00 2001 From: unknown Date: Sat, 3 Feb 2024 20:11:33 -0500 Subject: [PATCH 3/5] correct test files --- .../specimin/InterfaceImplementedTest.java | 9 +++------ .../specimin/InterfaceWithGenericTypeTest.java | 9 +++------ .../specimin/InterfaceWithUnsolvedSymbols.java | 9 +++------ .../checkerframework/specimin/UnsolvedInterfaceTest.java | 7 ++----- .../checkerframework/specimin/UnusedInterfaceTest.java | 7 ++----- 5 files changed, 13 insertions(+), 28 deletions(-) diff --git a/src/test/java/org/checkerframework/specimin/InterfaceImplementedTest.java b/src/test/java/org/checkerframework/specimin/InterfaceImplementedTest.java index 1e7e31db..31a5c0d2 100644 --- a/src/test/java/org/checkerframework/specimin/InterfaceImplementedTest.java +++ b/src/test/java/org/checkerframework/specimin/InterfaceImplementedTest.java @@ -1,17 +1,14 @@ package org.checkerframework.specimin; -import org.junit.Test; - import java.io.IOException; +import org.junit.Test; -/** - * This test checks if Specimin can properly remove unused method signatures from an interface. - */ +/** This test checks if Specimin can properly remove unused method signatures from an interface. */ public class InterfaceImplementedTest { @Test public void runTest() throws IOException { SpeciminTestExecutor.runTestWithoutJarPaths( - "unsolvedinterface", + "interfaceimplemented", new String[] {"com/example/Foo.java"}, new String[] {"com.example.Foo#doSomething()"}); } diff --git a/src/test/java/org/checkerframework/specimin/InterfaceWithGenericTypeTest.java b/src/test/java/org/checkerframework/specimin/InterfaceWithGenericTypeTest.java index bbd392ec..1d506ad1 100644 --- a/src/test/java/org/checkerframework/specimin/InterfaceWithGenericTypeTest.java +++ b/src/test/java/org/checkerframework/specimin/InterfaceWithGenericTypeTest.java @@ -1,18 +1,15 @@ package org.checkerframework.specimin; -import org.junit.Test; - import java.io.IOException; +import org.junit.Test; -/** - * This test checks if Specimin can work for interfaces with generic types. - */ +/** This test checks if Specimin can work for interfaces with generic types. */ public class InterfaceWithGenericTypeTest { @Test public void runTest() throws IOException { SpeciminTestExecutor.runTestWithoutJarPaths( "interfacewithgenerictype", new String[] {"com/example/Foo.java"}, - new String[] {"com.example.Foo#doSomething()"}); + new String[] {"com.example.Foo#doSomething(String)"}); } } diff --git a/src/test/java/org/checkerframework/specimin/InterfaceWithUnsolvedSymbols.java b/src/test/java/org/checkerframework/specimin/InterfaceWithUnsolvedSymbols.java index 96f89adb..86a4bb09 100644 --- a/src/test/java/org/checkerframework/specimin/InterfaceWithUnsolvedSymbols.java +++ b/src/test/java/org/checkerframework/specimin/InterfaceWithUnsolvedSymbols.java @@ -1,18 +1,15 @@ package org.checkerframework.specimin; -import org.junit.Test; - import java.io.IOException; +import org.junit.Test; -/** - * This test makes sure that Specimin will not crash if an interface contains unsolved symbols. - */ +/** This test makes sure that Specimin will not crash if an interface contains unsolved symbols. */ public class InterfaceWithUnsolvedSymbols { @Test public void runTest() throws IOException { SpeciminTestExecutor.runTestWithoutJarPaths( "interfacewithunsolvedsymbols", new String[] {"com/example/Foo.java"}, - new String[] {"com.example.Foo#doSomething()"}); + new String[] {"com.example.Foo#doSomething(String)"}); } } diff --git a/src/test/java/org/checkerframework/specimin/UnsolvedInterfaceTest.java b/src/test/java/org/checkerframework/specimin/UnsolvedInterfaceTest.java index 8aeef7c8..e51f2b5f 100644 --- a/src/test/java/org/checkerframework/specimin/UnsolvedInterfaceTest.java +++ b/src/test/java/org/checkerframework/specimin/UnsolvedInterfaceTest.java @@ -1,12 +1,9 @@ package org.checkerframework.specimin; -import org.junit.Test; - import java.io.IOException; +import org.junit.Test; -/** - * This test checks if Specimin can properly remove an unsolved interface. - */ +/** This test checks if Specimin can properly remove an unsolved interface. */ public class UnsolvedInterfaceTest { @Test public void runTest() throws IOException { diff --git a/src/test/java/org/checkerframework/specimin/UnusedInterfaceTest.java b/src/test/java/org/checkerframework/specimin/UnusedInterfaceTest.java index bb761166..5a669852 100644 --- a/src/test/java/org/checkerframework/specimin/UnusedInterfaceTest.java +++ b/src/test/java/org/checkerframework/specimin/UnusedInterfaceTest.java @@ -1,12 +1,9 @@ package org.checkerframework.specimin; -import org.junit.Test; - import java.io.IOException; +import org.junit.Test; -/** - * This test checks if Specimin can properly remove unused interfaces. - */ +/** This test checks if Specimin can properly remove unused interfaces. */ public class UnusedInterfaceTest { @Test public void runTest() throws IOException { From 1181390e8d2378a3700208afd3bcc7d3c4a6c952 Mon Sep 17 00:00:00 2001 From: unknown Date: Sat, 3 Feb 2024 20:16:38 -0500 Subject: [PATCH 4/5] remove comments from test files --- .../interfaceimplemented/expected/com/example/Baz.java | 1 - .../interfacewithgenerictype/expected/com/example/Baz.java | 2 -- 2 files changed, 3 deletions(-) diff --git a/src/test/resources/interfaceimplemented/expected/com/example/Baz.java b/src/test/resources/interfaceimplemented/expected/com/example/Baz.java index 0c362cdf..583e44fa 100644 --- a/src/test/resources/interfaceimplemented/expected/com/example/Baz.java +++ b/src/test/resources/interfaceimplemented/expected/com/example/Baz.java @@ -1,6 +1,5 @@ package com.example; -// Baz.java public interface Baz { void doSomething(); diff --git a/src/test/resources/interfacewithgenerictype/expected/com/example/Baz.java b/src/test/resources/interfacewithgenerictype/expected/com/example/Baz.java index 60d55ec9..4805dba8 100644 --- a/src/test/resources/interfacewithgenerictype/expected/com/example/Baz.java +++ b/src/test/resources/interfacewithgenerictype/expected/com/example/Baz.java @@ -1,10 +1,8 @@ package com.example; -// Baz.java public interface Baz { void doSomething(T value); - // sadly we can't remove this method. void doSomething(int x); } From a2a1c8a4e51eb6d2a2fc2e7dbcf4ebd9b4c414d3 Mon Sep 17 00:00:00 2001 From: unknown Date: Mon, 5 Feb 2024 17:20:19 -0500 Subject: [PATCH 5/5] Add method with unsolved types --- .../InterfaceMethodWithUnsolvedTypeTest.java | 18 ++++++++++++++++++ .../expected/com/example/Baz.java | 8 ++++++++ .../expected/com/example/Foo.java | 11 +++++++++++ .../expected/org/testing/UnsolvedType.java | 4 ++++ .../input/com/example/Baz.java | 11 +++++++++++ .../input/com/example/Foo.java | 11 +++++++++++ 6 files changed, 63 insertions(+) create mode 100644 src/test/java/org/checkerframework/specimin/InterfaceMethodWithUnsolvedTypeTest.java create mode 100644 src/test/resources/interfacemethodwithunsolvedtype/expected/com/example/Baz.java create mode 100644 src/test/resources/interfacemethodwithunsolvedtype/expected/com/example/Foo.java create mode 100644 src/test/resources/interfacemethodwithunsolvedtype/expected/org/testing/UnsolvedType.java create mode 100644 src/test/resources/interfacemethodwithunsolvedtype/input/com/example/Baz.java create mode 100644 src/test/resources/interfacemethodwithunsolvedtype/input/com/example/Foo.java diff --git a/src/test/java/org/checkerframework/specimin/InterfaceMethodWithUnsolvedTypeTest.java b/src/test/java/org/checkerframework/specimin/InterfaceMethodWithUnsolvedTypeTest.java new file mode 100644 index 00000000..b850e5ac --- /dev/null +++ b/src/test/java/org/checkerframework/specimin/InterfaceMethodWithUnsolvedTypeTest.java @@ -0,0 +1,18 @@ +package org.checkerframework.specimin; + +import java.io.IOException; +import org.junit.Test; + +/** + * This test makes sure that Specimin will not crash if methods from interfaces have unsolved return + * types. + */ +public class InterfaceMethodWithUnsolvedTypeTest { + @Test + public void runTest() throws IOException { + SpeciminTestExecutor.runTestWithoutJarPaths( + "interfacemethodwithunsolvedtype", + new String[] {"com/example/Foo.java"}, + new String[] {"com.example.Foo#doSomething(String)"}); + } +} diff --git a/src/test/resources/interfacemethodwithunsolvedtype/expected/com/example/Baz.java b/src/test/resources/interfacemethodwithunsolvedtype/expected/com/example/Baz.java new file mode 100644 index 00000000..91f5dad2 --- /dev/null +++ b/src/test/resources/interfacemethodwithunsolvedtype/expected/com/example/Baz.java @@ -0,0 +1,8 @@ +package com.example; + +import org.testing.UnsolvedType; + +public interface Baz { + + UnsolvedType doSomething(T value); +} diff --git a/src/test/resources/interfacemethodwithunsolvedtype/expected/com/example/Foo.java b/src/test/resources/interfacemethodwithunsolvedtype/expected/com/example/Foo.java new file mode 100644 index 00000000..1655861f --- /dev/null +++ b/src/test/resources/interfacemethodwithunsolvedtype/expected/com/example/Foo.java @@ -0,0 +1,11 @@ +package com.example; + +import org.testing.UnsolvedType; + +class Foo implements Baz { + + @Override + public UnsolvedType doSomething(String value) { + System.out.println("Foo is doing something with: " + value); + } +} diff --git a/src/test/resources/interfacemethodwithunsolvedtype/expected/org/testing/UnsolvedType.java b/src/test/resources/interfacemethodwithunsolvedtype/expected/org/testing/UnsolvedType.java new file mode 100644 index 00000000..33f33a27 --- /dev/null +++ b/src/test/resources/interfacemethodwithunsolvedtype/expected/org/testing/UnsolvedType.java @@ -0,0 +1,4 @@ +package org.testing; + +public class UnsolvedType { +} diff --git a/src/test/resources/interfacemethodwithunsolvedtype/input/com/example/Baz.java b/src/test/resources/interfacemethodwithunsolvedtype/input/com/example/Baz.java new file mode 100644 index 00000000..f4f9e4f0 --- /dev/null +++ b/src/test/resources/interfacemethodwithunsolvedtype/input/com/example/Baz.java @@ -0,0 +1,11 @@ +package com.example; + +import org.testing.UnsolvedType; + +public interface Baz { + UnsolvedType doSomething(T value); + UnsolvedType doNothing(int x); + +} + + diff --git a/src/test/resources/interfacemethodwithunsolvedtype/input/com/example/Foo.java b/src/test/resources/interfacemethodwithunsolvedtype/input/com/example/Foo.java new file mode 100644 index 00000000..239ae4fa --- /dev/null +++ b/src/test/resources/interfacemethodwithunsolvedtype/input/com/example/Foo.java @@ -0,0 +1,11 @@ +package com.example; + +import org.testing.UnsolvedType; + +class Foo implements Baz { + @Override + public UnsolvedType doSomething(String value) { + System.out.println("Foo is doing something with: " + value); + } + +}