diff --git a/src/main/java/org/checkerframework/specimin/UnsolvedSymbolVisitor.java b/src/main/java/org/checkerframework/specimin/UnsolvedSymbolVisitor.java index aaba7f01..d2d7e20c 100644 --- a/src/main/java/org/checkerframework/specimin/UnsolvedSymbolVisitor.java +++ b/src/main/java/org/checkerframework/specimin/UnsolvedSymbolVisitor.java @@ -20,6 +20,7 @@ import com.github.javaparser.ast.expr.InstanceOfExpr; import com.github.javaparser.ast.expr.LambdaExpr; import com.github.javaparser.ast.expr.MethodCallExpr; +import com.github.javaparser.ast.expr.MethodReferenceExpr; import com.github.javaparser.ast.expr.NameExpr; import com.github.javaparser.ast.expr.ObjectCreationExpr; import com.github.javaparser.ast.expr.PatternExpr; @@ -1069,6 +1070,36 @@ public Visitable visit(FieldAccessExpr node, Void p) { return super.visit(node, p); } + @Override + public Visitable visit(MethodReferenceExpr node, Void p) { + if (insideTargetMember) { + // TODO: handle all of the possible forms listed in JLS 15.13, not just the simplest + Expression scope = node.getScope(); + if (scope.isTypeExpr()) { + Type scopeAsType = scope.asTypeExpr().getType(); + String scopeAsTypeFQN = scopeAsType.asString(); + if (!isAClassPath(scopeAsTypeFQN) && scopeAsType.isClassOrInterfaceType()) { + scopeAsTypeFQN = + getQualifiedNameForClassOrInterfaceType(scopeAsType.asClassOrInterfaceType()); + } + if (classfileIsInOriginalCodebase(scopeAsTypeFQN)) { + addedTargetFiles.add(qualifiedNameToFilePath(scopeAsTypeFQN)); + } else { + // TODO: create a synthetic class? + } + } + String identifier = node.getIdentifier(); + // can be either the name of a method or "new" + if ("new".equals(identifier)) { + // TODO: figure out how to handle this case + System.err.println("Specimin warning: new in method references is not supported: " + node); + return super.visit(node, p); + } + potentialUsedMembers.add(identifier); + } + return super.visit(node, p); + } + @Override public Visitable visit(MethodCallExpr method, Void p) { /* @@ -2169,8 +2200,8 @@ public static boolean canSolveArguments(NodeList argList) { return true; } for (Expression arg : argList) { - if (arg.isLambdaExpr()) { - // Skip lambdas here and treat them specially later. + if (arg.isLambdaExpr() || arg.isMethodReferenceExpr()) { + // Skip lambdas and method refs here and treat them specially later. continue; } if (!canBeSolved(arg)) { @@ -2263,6 +2294,12 @@ private List getArgumentTypesImpl( LambdaExpr lambda = arg.asLambdaExpr(); parametersList.add(resolveLambdaType(lambda, pkgName)); continue; + } else if (arg.isMethodReferenceExpr()) { + // TODO: is there a better way to handle this? How should we know + // what the type is? The method ref is sometimes not solvable here. + // Maybe we will need to handle this in JavaTypeCorrect? + parametersList.add("java.util.function.Supplier"); + continue; } ResolvedType type = arg.calculateResolvedType(); diff --git a/src/test/java/org/checkerframework/specimin/MethodRef2Test.java b/src/test/java/org/checkerframework/specimin/MethodRef2Test.java new file mode 100644 index 00000000..66fb9486 --- /dev/null +++ b/src/test/java/org/checkerframework/specimin/MethodRef2Test.java @@ -0,0 +1,18 @@ +package org.checkerframework.specimin; + +import java.io.IOException; +import org.junit.Test; + +/** + * A test for a crash related to method signatures that occurred in Randoop: + * https://github.com/njit-jerse/specimin/issues/154 + */ +public class MethodRef2Test { + @Test + public void runTest() throws IOException { + SpeciminTestExecutor.runTestWithoutJarPaths( + "methodref2", + new String[] {"com/example/Simple.java"}, + new String[] {"com.example.Simple#bar()"}); + } +} diff --git a/src/test/resources/methodref2/expected/com/example/MethodSignature.java b/src/test/resources/methodref2/expected/com/example/MethodSignature.java new file mode 100644 index 00000000..3b5aba00 --- /dev/null +++ b/src/test/resources/methodref2/expected/com/example/MethodSignature.java @@ -0,0 +1,8 @@ +package com.example; + +public class MethodSignature { + + public String toString() { + throw new Error(); + } +} \ No newline at end of file diff --git a/src/test/resources/methodref2/expected/com/example/Simple.java b/src/test/resources/methodref2/expected/com/example/Simple.java new file mode 100644 index 00000000..3cf9bdb5 --- /dev/null +++ b/src/test/resources/methodref2/expected/com/example/Simple.java @@ -0,0 +1,15 @@ +package com.example; + +import java.util.Map; +import java.util.List; + +import org.plumelib.util.CollectionsPlume; + +class Simple { + + Map replacementMap; + + void bar() { + List signatureList = CollectionsPlume.mapList(MethodSignature::toString, replacementMap.keySet()); + } +} diff --git a/src/test/resources/methodref2/expected/org/plumelib/util/CollectionsPlume.java b/src/test/resources/methodref2/expected/org/plumelib/util/CollectionsPlume.java new file mode 100644 index 00000000..17dc7a55 --- /dev/null +++ b/src/test/resources/methodref2/expected/org/plumelib/util/CollectionsPlume.java @@ -0,0 +1,8 @@ +package org.plumelib.util; + +public class CollectionsPlume { + + public static OrgPlumelibUtilCollectionsPlumeMapListReturnType mapList(java.util.function.Supplier parameter0, java.util.Set parameter1) { + throw new Error(); + } +} \ No newline at end of file diff --git a/src/test/resources/methodref2/expected/org/plumelib/util/OrgPlumelibUtilCollectionsPlumeMapListReturnType.java b/src/test/resources/methodref2/expected/org/plumelib/util/OrgPlumelibUtilCollectionsPlumeMapListReturnType.java new file mode 100644 index 00000000..0e3db04f --- /dev/null +++ b/src/test/resources/methodref2/expected/org/plumelib/util/OrgPlumelibUtilCollectionsPlumeMapListReturnType.java @@ -0,0 +1,4 @@ +package org.plumelib.util; + +public class OrgPlumelibUtilCollectionsPlumeMapListReturnType { +} \ No newline at end of file diff --git a/src/test/resources/methodref2/input/com/example/MethodSignature.java b/src/test/resources/methodref2/input/com/example/MethodSignature.java new file mode 100644 index 00000000..edc60f18 --- /dev/null +++ b/src/test/resources/methodref2/input/com/example/MethodSignature.java @@ -0,0 +1,8 @@ +package com.example; + +public class MethodSignature { + @Override + public String toString() { + return "a string"; + } +} \ No newline at end of file diff --git a/src/test/resources/methodref2/input/com/example/Simple.java b/src/test/resources/methodref2/input/com/example/Simple.java new file mode 100644 index 00000000..3a0d39cc --- /dev/null +++ b/src/test/resources/methodref2/input/com/example/Simple.java @@ -0,0 +1,17 @@ +package com.example; + +import java.util.Map; +import java.util.List; + +import org.plumelib.util.CollectionsPlume; + +class Simple { + + Map replacementMap; + + // Target method. + void bar() { + List signatureList = + CollectionsPlume.mapList(MethodSignature::toString, replacementMap.keySet()); + } +} diff --git a/typecheck_test_outputs.sh b/typecheck_test_outputs.sh index 8aeb8c8a..a76958d5 100755 --- a/typecheck_test_outputs.sh +++ b/typecheck_test_outputs.sh @@ -14,6 +14,9 @@ for testcase in * ; do if [ "${testcase}" = "shared" ]; then continue; fi # https://bugs.openjdk.org/browse/JDK-8319461 wasn't actually fixed (this test is based on that bug) if [ "${testcase}" = "superinterfaceextends" ]; then continue; fi + # incomplete handling of method references: https://github.com/njit-jerse/specimin/issues/291 + # this test exists to check that no crash occurs, not that Specimin produces the correct output + if [ "${testcase}" = "methodref2" ]; then continue; fi cd "${testcase}/expected/" || exit 1 # javac relies on word splitting # shellcheck disable=SC2046