From ef273ac4783731368e3c3236b2fe7ef945bfadd0 Mon Sep 17 00:00:00 2001 From: Theron Wang Date: Thu, 25 Jul 2024 15:50:23 -0400 Subject: [PATCH 1/3] functional interface check if member is used --- src/main/java/org/checkerframework/specimin/PrunerVisitor.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/org/checkerframework/specimin/PrunerVisitor.java b/src/main/java/org/checkerframework/specimin/PrunerVisitor.java index f6109c462..b4fae8ada 100644 --- a/src/main/java/org/checkerframework/specimin/PrunerVisitor.java +++ b/src/main/java/org/checkerframework/specimin/PrunerVisitor.java @@ -348,7 +348,7 @@ public Visitable visit(MethodDeclaration methodDecl, Void p) { return methodDecl; } - if (insideFunctionalInterface) { + if (insideFunctionalInterface && usedMembers.contains(signature)) { if (methodDecl.getBody().isPresent()) { // avoid introducing unsolved symbols into the final output. methodDecl.setBody(StaticJavaParser.parseBlock("{ throw new Error(); }")); From b40c30b92b53dd414396e4711133bab51574a8cb Mon Sep 17 00:00:00 2001 From: Theron Wang Date: Thu, 25 Jul 2024 16:30:50 -0400 Subject: [PATCH 2/3] ensure abstract method in functional interfaces is preserved if used --- .../checkerframework/specimin/PrunerVisitor.java | 16 ++++++++-------- .../specimin/TargetMethodFinderVisitor.java | 13 +++++++++++++ 2 files changed, 21 insertions(+), 8 deletions(-) diff --git a/src/main/java/org/checkerframework/specimin/PrunerVisitor.java b/src/main/java/org/checkerframework/specimin/PrunerVisitor.java index b4fae8ada..52a42390c 100644 --- a/src/main/java/org/checkerframework/specimin/PrunerVisitor.java +++ b/src/main/java/org/checkerframework/specimin/PrunerVisitor.java @@ -335,6 +335,14 @@ public Visitable visit(MethodDeclaration methodDecl, Void p) { return super.visit(methodDecl, p); } + if (insideFunctionalInterface && usedMembers.contains(signature)) { + if (methodDecl.getBody().isPresent()) { + // avoid introducing unsolved symbols into the final output. + methodDecl.setBody(StaticJavaParser.parseBlock("{ throw new Error(); }")); + } + return methodDecl; + } + if (usedMembers.contains(signature) || isAResolvedYetStuckMethod(methodDecl)) { boolean isMethodInsideInterface = isInsideInterface(methodDecl); // do nothing if methodDecl is just a method signature in a class. @@ -348,14 +356,6 @@ public Visitable visit(MethodDeclaration methodDecl, Void p) { return methodDecl; } - if (insideFunctionalInterface && usedMembers.contains(signature)) { - if (methodDecl.getBody().isPresent()) { - // avoid introducing unsolved symbols into the final output. - methodDecl.setBody(StaticJavaParser.parseBlock("{ throw new Error(); }")); - } - return methodDecl; - } - // if insideTargetMethod is true, this current method declaration belongs to an anonnymous // class inside the target method. if (!insideTargetMember) { diff --git a/src/main/java/org/checkerframework/specimin/TargetMethodFinderVisitor.java b/src/main/java/org/checkerframework/specimin/TargetMethodFinderVisitor.java index 11e6865be..ff3de5345 100644 --- a/src/main/java/org/checkerframework/specimin/TargetMethodFinderVisitor.java +++ b/src/main/java/org/checkerframework/specimin/TargetMethodFinderVisitor.java @@ -26,6 +26,7 @@ import com.github.javaparser.ast.type.Type; import com.github.javaparser.ast.type.UnionType; import com.github.javaparser.ast.visitor.Visitable; +import com.github.javaparser.resolution.MethodUsage; import com.github.javaparser.resolution.UnsolvedSymbolException; import com.github.javaparser.resolution.declarations.ResolvedConstructorDeclaration; import com.github.javaparser.resolution.declarations.ResolvedEnumConstantDeclaration; @@ -470,6 +471,18 @@ public Visitable visit(MethodCallExpr call, Void p) { Expression arg = call.getArgument(i); if (arg.isLambdaExpr()) { updateUsedClassBasedOnType(decl.getParam(i).getType()); + // We should mark the abstract method for preservation as well + if (decl.getParam(i).getType().isReferenceType()) { + ResolvedReferenceType functionalInterface = + decl.getParam(i).getType().asReferenceType(); + for (MethodUsage method : functionalInterface.getDeclaredMethods()) { + if (method.getDeclaration().isAbstract()) { + preserveMethodDecl(method.getDeclaration()); + // Only one abstract method per functional interface + break; + } + } + } } } } From 9d83283f0795cbde004378f17327c87321fdc529 Mon Sep 17 00:00:00 2001 From: Theron Wang Date: Thu, 25 Jul 2024 17:24:50 -0400 Subject: [PATCH 3/3] add test --- .../UnusedFuncInterfaceMethodTest.java | 18 ++++++++++++++++++ .../expected/com/example/Bar.java | 8 ++++++++ .../example/ComExampleBarUseReturnType.java | 4 ++++ .../com/example/FuncInterfaceUnused.java | 4 ++++ .../expected/com/example/Simple.java | 8 ++++++++ .../com/example/SyntheticFunction4.java | 6 ++++++ .../input/com/example/FuncInterfaceUnused.java | 6 ++++++ .../input/com/example/Simple.java | 11 +++++++++++ 8 files changed, 65 insertions(+) create mode 100644 src/test/java/org/checkerframework/specimin/UnusedFuncInterfaceMethodTest.java create mode 100644 src/test/resources/unusedfuncinterfacemethod/expected/com/example/Bar.java create mode 100644 src/test/resources/unusedfuncinterfacemethod/expected/com/example/ComExampleBarUseReturnType.java create mode 100644 src/test/resources/unusedfuncinterfacemethod/expected/com/example/FuncInterfaceUnused.java create mode 100644 src/test/resources/unusedfuncinterfacemethod/expected/com/example/Simple.java create mode 100644 src/test/resources/unusedfuncinterfacemethod/expected/com/example/SyntheticFunction4.java create mode 100644 src/test/resources/unusedfuncinterfacemethod/input/com/example/FuncInterfaceUnused.java create mode 100644 src/test/resources/unusedfuncinterfacemethod/input/com/example/Simple.java diff --git a/src/test/java/org/checkerframework/specimin/UnusedFuncInterfaceMethodTest.java b/src/test/java/org/checkerframework/specimin/UnusedFuncInterfaceMethodTest.java new file mode 100644 index 000000000..1491d0ccb --- /dev/null +++ b/src/test/java/org/checkerframework/specimin/UnusedFuncInterfaceMethodTest.java @@ -0,0 +1,18 @@ +package org.checkerframework.specimin; + +import java.io.IOException; +import org.junit.Test; + +/** + * This test checks that if a functional interface has an unused method, it is removed, and if a + * synthetic functional interface method is generated, it is preserved. + */ +public class UnusedFuncInterfaceMethodTest { + @Test + public void runTest() throws IOException { + SpeciminTestExecutor.runTestWithoutJarPaths( + "unusedfuncinterfacemethod", + new String[] {"com/example/Simple.java"}, + new String[] {"com.example.Simple#foo()"}); + } +} diff --git a/src/test/resources/unusedfuncinterfacemethod/expected/com/example/Bar.java b/src/test/resources/unusedfuncinterfacemethod/expected/com/example/Bar.java new file mode 100644 index 000000000..c49ad01c6 --- /dev/null +++ b/src/test/resources/unusedfuncinterfacemethod/expected/com/example/Bar.java @@ -0,0 +1,8 @@ +package com.example; + +public class Bar { + + public static ComExampleBarUseReturnType use(SyntheticFunction4 parameter0) { + throw new Error(); + } +} diff --git a/src/test/resources/unusedfuncinterfacemethod/expected/com/example/ComExampleBarUseReturnType.java b/src/test/resources/unusedfuncinterfacemethod/expected/com/example/ComExampleBarUseReturnType.java new file mode 100644 index 000000000..e2b5b0351 --- /dev/null +++ b/src/test/resources/unusedfuncinterfacemethod/expected/com/example/ComExampleBarUseReturnType.java @@ -0,0 +1,4 @@ +package com.example; + +public class ComExampleBarUseReturnType { +} diff --git a/src/test/resources/unusedfuncinterfacemethod/expected/com/example/FuncInterfaceUnused.java b/src/test/resources/unusedfuncinterfacemethod/expected/com/example/FuncInterfaceUnused.java new file mode 100644 index 000000000..6415d304c --- /dev/null +++ b/src/test/resources/unusedfuncinterfacemethod/expected/com/example/FuncInterfaceUnused.java @@ -0,0 +1,4 @@ +package com.example; + +public interface FuncInterfaceUnused { +} diff --git a/src/test/resources/unusedfuncinterfacemethod/expected/com/example/Simple.java b/src/test/resources/unusedfuncinterfacemethod/expected/com/example/Simple.java new file mode 100644 index 000000000..3ab58a8bc --- /dev/null +++ b/src/test/resources/unusedfuncinterfacemethod/expected/com/example/Simple.java @@ -0,0 +1,8 @@ +package com.example; + +public class Simple implements FuncInterfaceUnused { + + public void foo() { + Bar.use((a, b, c, d) -> 0); + } +} diff --git a/src/test/resources/unusedfuncinterfacemethod/expected/com/example/SyntheticFunction4.java b/src/test/resources/unusedfuncinterfacemethod/expected/com/example/SyntheticFunction4.java new file mode 100644 index 000000000..684d580d2 --- /dev/null +++ b/src/test/resources/unusedfuncinterfacemethod/expected/com/example/SyntheticFunction4.java @@ -0,0 +1,6 @@ +package com.example; + +public interface SyntheticFunction4 { + + public T4 apply(T parameter0, T1 parameter1, T2 parameter2, T3 parameter3); +} diff --git a/src/test/resources/unusedfuncinterfacemethod/input/com/example/FuncInterfaceUnused.java b/src/test/resources/unusedfuncinterfacemethod/input/com/example/FuncInterfaceUnused.java new file mode 100644 index 000000000..d3ab62673 --- /dev/null +++ b/src/test/resources/unusedfuncinterfacemethod/input/com/example/FuncInterfaceUnused.java @@ -0,0 +1,6 @@ +package com.example; + +@FunctionalInterface +public interface FuncInterfaceUnused { + void shouldBeRemoved(); +} diff --git a/src/test/resources/unusedfuncinterfacemethod/input/com/example/Simple.java b/src/test/resources/unusedfuncinterfacemethod/input/com/example/Simple.java new file mode 100644 index 000000000..d8482bb6b --- /dev/null +++ b/src/test/resources/unusedfuncinterfacemethod/input/com/example/Simple.java @@ -0,0 +1,11 @@ +package com.example; + +public class Simple implements FuncInterfaceUnused { + public void foo() { + Bar.use((a, b, c, d) -> 0); + } + + public void shouldBeRemoved() { + + } +}