From 101bdc66b37c007a754d6b60d541218317184bb4 Mon Sep 17 00:00:00 2001 From: unknown Date: Mon, 13 Nov 2023 14:58:46 -0500 Subject: [PATCH 01/10] add codes for static methods --- .../specimin/TargetMethodFinderVisitor.java | 5 ++ .../specimin/UnsolvedSymbolVisitor.java | 54 ++++++++++++++++--- .../specimin/UnsolvedStaticMethod.java | 18 +++++++ .../expected/com/example/Class.java | 8 +++ .../com/example/ProcessReturnType.java | 4 ++ .../expected/com/example/Simple.java | 10 ++++ .../input/com/example/Simple.java | 10 ++++ tempDirJDK/com/example/Simple.java | 8 --- .../ast/body/GetTypeReturnType.java | 7 --- .../ast/body/MethodDeclaration.java | 7 --- 10 files changed, 103 insertions(+), 28 deletions(-) create mode 100644 src/test/java/org/checkerframework/specimin/UnsolvedStaticMethod.java create mode 100644 src/test/resources/unsolvedstaticmethod/expected/com/example/Class.java create mode 100644 src/test/resources/unsolvedstaticmethod/expected/com/example/ProcessReturnType.java create mode 100644 src/test/resources/unsolvedstaticmethod/expected/com/example/Simple.java create mode 100644 src/test/resources/unsolvedstaticmethod/input/com/example/Simple.java delete mode 100644 tempDirJDK/com/example/Simple.java delete mode 100644 tempDirJDK/com/github/javaparser/ast/body/GetTypeReturnType.java delete mode 100644 tempDirJDK/com/github/javaparser/ast/body/MethodDeclaration.java diff --git a/src/main/java/org/checkerframework/specimin/TargetMethodFinderVisitor.java b/src/main/java/org/checkerframework/specimin/TargetMethodFinderVisitor.java index 34e8a9e64..79a74a78b 100644 --- a/src/main/java/org/checkerframework/specimin/TargetMethodFinderVisitor.java +++ b/src/main/java/org/checkerframework/specimin/TargetMethodFinderVisitor.java @@ -19,6 +19,7 @@ import com.github.javaparser.resolution.declarations.ResolvedFieldDeclaration; import com.github.javaparser.resolution.declarations.ResolvedValueDeclaration; import com.github.javaparser.resolution.types.ResolvedReferenceType; +import com.github.javaparser.resolution.types.ResolvedType; import java.util.ArrayList; import java.util.HashSet; import java.util.List; @@ -211,6 +212,10 @@ public Visitable visit(MethodCallExpr call, Void p) { if (insideTargetMethod) { usedMembers.add(call.resolve().getQualifiedSignature()); usedClass.add(call.resolve().getPackageName() + "." + call.resolve().getClassName()); + ResolvedType methodReturnType = call.resolve().getReturnType(); + if (methodReturnType instanceof ResolvedReferenceType) { + usedClass.add(methodReturnType.asReferenceType().getQualifiedName()); + } } return super.visit(call, p); } diff --git a/src/main/java/org/checkerframework/specimin/UnsolvedSymbolVisitor.java b/src/main/java/org/checkerframework/specimin/UnsolvedSymbolVisitor.java index 2146db792..9ce851509 100644 --- a/src/main/java/org/checkerframework/specimin/UnsolvedSymbolVisitor.java +++ b/src/main/java/org/checkerframework/specimin/UnsolvedSymbolVisitor.java @@ -531,11 +531,16 @@ public Visitable visit(MethodCallExpr method, Void p) { return super.visit(method, p); } if (unsolvedAndNotSimple(method)) { - updateClassSetWithNotSimpleMethodCall(method); + updateClassSetWithNotSimpleMethodCall(method.toString(), getArgumentsFromMethodCall(method)); } else if (calledByAnIncompleteSyntheticClass(method)) { @ClassGetSimpleName String incompleteClassName = getSyntheticClass(method); updateUnsolvedClassWithMethod(method, incompleteClassName, ""); } + if (unsolvedAndCalledByASimpleClassName(method)) { + String methodFullyQualifiedCall = toFullyQualifiedCall(method); + updateClassSetWithNotSimpleMethodCall( + methodFullyQualifiedCall, getArgumentsFromMethodCall(method)); + } this.gotException = calledByAnUnsolvedSymbol(method) || calledByAnIncompleteSyntheticClass(method) @@ -1228,6 +1233,43 @@ public static UnsolvedClass getSimpleSyntheticClassFromFullyQualifiedName( return new UnsolvedClass(className, packageName); } + /** + * Checks whether a method call, invoked by a simple class name, is unsolved. + * + * @param method the method call to be examined + * @return true if the method is unsolved and called by a simple class name, otherwise false + */ + public boolean unsolvedAndCalledByASimpleClassName(MethodCallExpr method) { + try { + method.resolve(); + return false; + } catch (Exception e) { + Optional callerExpression = method.getScope(); + if (callerExpression.isEmpty()) { + return false; + } + return classAndPackageMap.containsKey(callerExpression.get().toString()); + } + } + + /** + * Returns the fully-qualified class name version of a method call invoked by a simple class name. + * + * @param method the method call invoked by a simple class name + * @return the String representation of the method call with a fully-qualified class name + */ + public String toFullyQualifiedCall(MethodCallExpr method) { + if (!unsolvedAndCalledByASimpleClassName(method)) { + throw new RuntimeException( + "Before running convertSimpleCallToFullyQualifiedCall, check if the method call is called" + + " by a simple class name with calledByASimpleClassName"); + } + String methodCall = method.toString(); + String classCaller = method.getScope().get().toString(); + String packageOfClass = this.classAndPackageMap.get(classCaller); + return packageOfClass + "." + methodCall; + } + /** * This method checks if a method call is not-simple and unsolved. In this context, we declare a * not-simple method call as a method that is directly called by a qualified class name. For @@ -1256,10 +1298,11 @@ public static boolean unsolvedAndNotSimple(MethodCallExpr method) { * For a method call that is not simple, this method will take that method as input and create * corresponding synthetic class * - * @param method the method call to be taken as input + * @param methodCall the method call to be taken as input + * @param methodAgruments the list of agruments for this method call */ - public void updateClassSetWithNotSimpleMethodCall(MethodCallExpr method) { - String methodCall = method.toString(); + public void updateClassSetWithNotSimpleMethodCall( + String methodCall, List methodAgruments) { String methodCallWithoutParen = methodCall.replace("()", ""); List methodParts = Splitter.onPattern("[.]").splitToList(methodCallWithoutParen); int lengthMethodParts = methodParts.size(); @@ -1281,8 +1324,7 @@ public void updateClassSetWithNotSimpleMethodCall(MethodCallExpr method) { @SuppressWarnings("signature") @ClassGetSimpleName String thisReturnType = returnTypeClassName; UnsolvedClass newClass = new UnsolvedClass(thisReturnType, packageName); - UnsolvedMethod newMethod = - new UnsolvedMethod(methodName, thisReturnType, getArgumentsFromMethodCall(method)); + UnsolvedMethod newMethod = new UnsolvedMethod(methodName, thisReturnType, methodAgruments); newClass.addMethod(newMethod); syntheticMethodAndClass.put(newMethod.toString(), newClass); @SuppressWarnings( diff --git a/src/test/java/org/checkerframework/specimin/UnsolvedStaticMethod.java b/src/test/java/org/checkerframework/specimin/UnsolvedStaticMethod.java new file mode 100644 index 000000000..770dd6048 --- /dev/null +++ b/src/test/java/org/checkerframework/specimin/UnsolvedStaticMethod.java @@ -0,0 +1,18 @@ +package org.checkerframework.specimin; + +import java.io.IOException; +import org.junit.Test; + +/** + * This test checks if Specimin will work for input files that contain unsolved static methods that + * invoked by a simple class name. + */ +public class UnsolvedStaticMethod { + @Test + public void runTest() throws IOException { + SpeciminTestExecutor.runTestWithoutJarPaths( + "unsolvedstaticmethod", + new String[] {"com/example/Simple.java"}, + new String[] {"com.example.Simple#bar()"}); + } +} diff --git a/src/test/resources/unsolvedstaticmethod/expected/com/example/Class.java b/src/test/resources/unsolvedstaticmethod/expected/com/example/Class.java new file mode 100644 index 000000000..f65973071 --- /dev/null +++ b/src/test/resources/unsolvedstaticmethod/expected/com/example/Class.java @@ -0,0 +1,8 @@ +package com.example; + +public class Class { + + public ProcessReturnType process() { + throw new Error(); + } +} diff --git a/src/test/resources/unsolvedstaticmethod/expected/com/example/ProcessReturnType.java b/src/test/resources/unsolvedstaticmethod/expected/com/example/ProcessReturnType.java new file mode 100644 index 000000000..97a4448cb --- /dev/null +++ b/src/test/resources/unsolvedstaticmethod/expected/com/example/ProcessReturnType.java @@ -0,0 +1,4 @@ +package com.example; + +public class ProcessReturnType { +} diff --git a/src/test/resources/unsolvedstaticmethod/expected/com/example/Simple.java b/src/test/resources/unsolvedstaticmethod/expected/com/example/Simple.java new file mode 100644 index 000000000..d3add7718 --- /dev/null +++ b/src/test/resources/unsolvedstaticmethod/expected/com/example/Simple.java @@ -0,0 +1,10 @@ +package com.example; + +import com.example.Class; + +class Simple { + + void bar() { + Class.process(); + } +} diff --git a/src/test/resources/unsolvedstaticmethod/input/com/example/Simple.java b/src/test/resources/unsolvedstaticmethod/input/com/example/Simple.java new file mode 100644 index 000000000..d405e510f --- /dev/null +++ b/src/test/resources/unsolvedstaticmethod/input/com/example/Simple.java @@ -0,0 +1,10 @@ +package com.example; + +import com.example.Class; + +class Simple { + // Target method. + void bar() { + Class.process(); + } +} diff --git a/tempDirJDK/com/example/Simple.java b/tempDirJDK/com/example/Simple.java deleted file mode 100644 index 001da8c80..000000000 --- a/tempDirJDK/com/example/Simple.java +++ /dev/null @@ -1,8 +0,0 @@ -package com.example; -import com.github.javaparser.ast.body.MethodDeclaration; - -public class Simple { - private boolean isVoidType (MethodDeclaration method) { - return method.getType().isVoidType(); - } -} diff --git a/tempDirJDK/com/github/javaparser/ast/body/GetTypeReturnType.java b/tempDirJDK/com/github/javaparser/ast/body/GetTypeReturnType.java deleted file mode 100644 index d2ef3e49e..000000000 --- a/tempDirJDK/com/github/javaparser/ast/body/GetTypeReturnType.java +++ /dev/null @@ -1,7 +0,0 @@ -package com.github.javaparser.ast.body; -public class GetTypeReturnType { - - public boolean isVoidType() { - throw new Error(); - } -} diff --git a/tempDirJDK/com/github/javaparser/ast/body/MethodDeclaration.java b/tempDirJDK/com/github/javaparser/ast/body/MethodDeclaration.java deleted file mode 100644 index 245f7ed2c..000000000 --- a/tempDirJDK/com/github/javaparser/ast/body/MethodDeclaration.java +++ /dev/null @@ -1,7 +0,0 @@ -package com.github.javaparser.ast.body; -public class MethodDeclaration { - - public GetTypeReturnType getType() { - throw new Error(); - } -} From 84762d31fcaa91b4ebee2101807f3df55617b236 Mon Sep 17 00:00:00 2001 From: unknown Date: Mon, 13 Nov 2023 15:50:11 -0500 Subject: [PATCH 02/10] not allow wildcard --- .../specimin/UnsolvedSymbolVisitor.java | 60 +++++++++---------- 1 file changed, 29 insertions(+), 31 deletions(-) diff --git a/src/main/java/org/checkerframework/specimin/UnsolvedSymbolVisitor.java b/src/main/java/org/checkerframework/specimin/UnsolvedSymbolVisitor.java index 9ce851509..91674be29 100644 --- a/src/main/java/org/checkerframework/specimin/UnsolvedSymbolVisitor.java +++ b/src/main/java/org/checkerframework/specimin/UnsolvedSymbolVisitor.java @@ -130,12 +130,6 @@ public class UnsolvedSymbolVisitor extends ModifierVisitor { /** This map the classes in the compilation unit with the related package */ private final Map classAndPackageMap = new HashMap<>(); - /** - * If there is any import statement that ends with *, this string will be replaced by one of the - * class from those import statements. - */ - private String chosenPackage = ""; - /** This set has fully-qualified class names that come from jar files input */ private final Set<@FullyQualifiedName String> classesFromJar = new HashSet<>(); @@ -207,12 +201,8 @@ private void setclassAndPackageMap() { String className = importParts.get(importParts.size() - 1); String packageName = importStatement.replace("." + className, ""); if (className.equals("*")) { - if (!chosenPackage.equals("")) { - throw new RuntimeException( - "Multiple wildcard import statements found. Please use explicit import" - + " statements."); - } - chosenPackage = packageName; + throw new RuntimeException( + "A wildcard import statement found. Please use explicit import" + " statements."); } else { this.classAndPackageMap.put(className, packageName); } @@ -476,11 +466,13 @@ public Visitable visit(MethodDeclaration node, Void arg) { try { nodeType.resolve(); } catch (UnsolvedSymbolException | UnsupportedOperationException e) { - UnsolvedClass syntheticType = - new UnsolvedClass( - nodeTypeSimpleForm, - classAndPackageMap.getOrDefault(nodeTypeSimpleForm, this.chosenPackage)); - this.updateMissingClass(syntheticType); + if (classAndPackageMap.containsKey(nodeTypeSimpleForm)) { + UnsolvedClass syntheticType = + new UnsolvedClass(nodeTypeSimpleForm, classAndPackageMap.get(nodeTypeSimpleForm)); + this.updateMissingClass(syntheticType); + } else { + throw new RuntimeException("Unexpected class: " + nodeTypeSimpleForm); + } } if (!insideAnObjectCreation) { @@ -567,10 +559,12 @@ public Visitable visit(Parameter parameter, Void p) { } else { // since it is unsolved, it could not be a primitive type @ClassGetSimpleName String className = parameter.getType().asClassOrInterfaceType().getName().asString(); - UnsolvedClass newClass = - new UnsolvedClass( - className, classAndPackageMap.getOrDefault(className, this.chosenPackage)); - updateMissingClass(newClass); + if (classAndPackageMap.containsKey(className)) { + UnsolvedClass newClass = new UnsolvedClass(className, classAndPackageMap.get(className)); + updateMissingClass(newClass); + } else { + throw new RuntimeException("Unexpected class: " + className); + } } } gotException = true; @@ -594,10 +588,14 @@ public Visitable visit(ObjectCreationExpr newExpr, Void p) { try { List argumentsCreation = getArgumentsFromObjectCreation(newExpr); UnsolvedMethod creationMethod = new UnsolvedMethod("", type, argumentsCreation); - UnsolvedClass newClass = - new UnsolvedClass(type, classAndPackageMap.getOrDefault(type, this.chosenPackage)); - newClass.addMethod(creationMethod); - this.updateMissingClass(newClass); + if (classAndPackageMap.containsKey(type)) { + UnsolvedClass newClass = new UnsolvedClass(type, classAndPackageMap.get(type)); + newClass.addMethod(creationMethod); + this.updateMissingClass(newClass); + } else { + throw new RuntimeException("Unexpected class: " + type); + } + } catch (Exception q) { // can not solve the parameters for this object creation in this current run } @@ -691,9 +689,10 @@ public void updateUnsolvedClassWithMethod( } else { returnType = desiredReturnType; } - UnsolvedClass missingClass = - new UnsolvedClass( - className, classAndPackageMap.getOrDefault(className, this.chosenPackage)); + if (!classAndPackageMap.containsKey(className)) { + throw new RuntimeException("Unexpected class: " + className); + } + UnsolvedClass missingClass = new UnsolvedClass(className, classAndPackageMap.get(className)); UnsolvedMethod thisMethod = new UnsolvedMethod(methodName, returnType, listOfParameters); missingClass.addMethod(thisMethod); syntheticMethodAndClass.put(methodName, missingClass); @@ -732,7 +731,7 @@ public boolean isFromAJarFile(Expression expr) { + ((MethodCallExpr) expr).resolve().getClassName(); } else if (expr instanceof ObjectCreationExpr) { String shortName = ((ObjectCreationExpr) expr).getTypeAsString(); - String packageName = classAndPackageMap.getOrDefault(shortName, this.chosenPackage); + String packageName = classAndPackageMap.get(shortName); className = packageName + "." + shortName; } else { throw new RuntimeException("Unexpected call: " + expr + ". Contact developers!"); @@ -1136,8 +1135,7 @@ public void updateSyntheticSourceCode() { * @param missedClass a synthetic class to be deleted */ public void deleteOldSyntheticClass(UnsolvedClass missedClass) { - String classPackage = - classAndPackageMap.getOrDefault(missedClass.getClassName(), this.chosenPackage); + String classPackage = classAndPackageMap.get(missedClass.getClassName()); String filePathStr = this.rootDirectory + classPackage + "/" + missedClass.getClassName() + ".java"; Path filePath = Path.of(filePathStr); From 78514fab42e99fe3d7e3c21ce7f87a0ca89c72eb Mon Sep 17 00:00:00 2001 From: unknown Date: Mon, 13 Nov 2023 21:46:44 -0500 Subject: [PATCH 03/10] add codes and test cases for static methods --- .../specimin/UnsolvedMethod.java | 13 ++++++ .../specimin/UnsolvedSymbolVisitor.java | 43 +++++++++++++------ .../expected/com/example/Class.java | 8 ---- .../ComExampleMyClassProcessReturnType.java | 4 ++ .../expected/com/example/MyClass.java | 8 ++++ .../com/example/ProcessReturnType.java | 4 -- .../expected/com/example/Simple.java | 5 ++- .../OrgTestingThisClassProcessReturnType.java | 4 ++ .../expected/org/testing/ThisClass.java | 8 ++++ 9 files changed, 69 insertions(+), 28 deletions(-) delete mode 100644 src/test/resources/unsolvedstaticmethod/expected/com/example/Class.java create mode 100644 src/test/resources/unsolvedstaticmethod/expected/com/example/ComExampleMyClassProcessReturnType.java create mode 100644 src/test/resources/unsolvedstaticmethod/expected/com/example/MyClass.java delete mode 100644 src/test/resources/unsolvedstaticmethod/expected/com/example/ProcessReturnType.java create mode 100644 src/test/resources/unsolvedstaticmethod/expected/org/testing/OrgTestingThisClassProcessReturnType.java create mode 100644 src/test/resources/unsolvedstaticmethod/expected/org/testing/ThisClass.java diff --git a/src/main/java/org/checkerframework/specimin/UnsolvedMethod.java b/src/main/java/org/checkerframework/specimin/UnsolvedMethod.java index fa2a4588b..33b6211fe 100644 --- a/src/main/java/org/checkerframework/specimin/UnsolvedMethod.java +++ b/src/main/java/org/checkerframework/specimin/UnsolvedMethod.java @@ -23,6 +23,9 @@ public class UnsolvedMethod { */ private List parameterList; + /** This field is set to true if this method is a static method */ + private boolean isStatic = false; + /** * Create an instance of UnsolvedMethod * @@ -65,6 +68,11 @@ public String getName() { return name; } + /** Set isStatic to true */ + public void setStatic() { + isStatic = true; + } + /** * Return the content of the method. Note that the body of the method is stubbed out. * @@ -85,7 +93,12 @@ public String toString() { if (!returnType.equals("")) { returnTypeInString = returnType + " "; } + String staticField = ""; + if (isStatic) { + staticField = "static "; + } return "\n public " + + staticField + returnTypeInString + name + "(" diff --git a/src/main/java/org/checkerframework/specimin/UnsolvedSymbolVisitor.java b/src/main/java/org/checkerframework/specimin/UnsolvedSymbolVisitor.java index 91674be29..b6fcb31d9 100644 --- a/src/main/java/org/checkerframework/specimin/UnsolvedSymbolVisitor.java +++ b/src/main/java/org/checkerframework/specimin/UnsolvedSymbolVisitor.java @@ -527,12 +527,12 @@ public Visitable visit(MethodCallExpr method, Void p) { } else if (calledByAnIncompleteSyntheticClass(method)) { @ClassGetSimpleName String incompleteClassName = getSyntheticClass(method); updateUnsolvedClassWithMethod(method, incompleteClassName, ""); - } - if (unsolvedAndCalledByASimpleClassName(method)) { + } else if (unsolvedAndCalledByASimpleClassName(method)) { String methodFullyQualifiedCall = toFullyQualifiedCall(method); updateClassSetWithNotSimpleMethodCall( methodFullyQualifiedCall, getArgumentsFromMethodCall(method)); } + this.gotException = calledByAnUnsolvedSymbol(method) || calledByAnIncompleteSyntheticClass(method) @@ -1219,7 +1219,7 @@ public static boolean isAClassPath(String potentialClassPath) { * @param fullyName the fully-qualified name of the class * @return the corresponding instance of UnsolvedClass */ - public static UnsolvedClass getSimpleSyntheticClassFromFullyQualifiedName( + public UnsolvedClass getSimpleSyntheticClassFromFullyQualifiedName( @FullyQualifiedName String fullyName) { if (!isAClassPath(fullyName)) { throw new RuntimeException( @@ -1278,7 +1278,7 @@ public String toFullyQualifiedCall(MethodCallExpr method) { * @param method the method call to be checked * @return true if the method call is not simple and unsolved */ - public static boolean unsolvedAndNotSimple(MethodCallExpr method) { + public boolean unsolvedAndNotSimple(MethodCallExpr method) { try { method.resolve().getReturnType(); return false; @@ -1294,13 +1294,17 @@ public static boolean unsolvedAndNotSimple(MethodCallExpr method) { /** * For a method call that is not simple, this method will take that method as input and create - * corresponding synthetic class + * corresponding synthetic class. * * @param methodCall the method call to be taken as input * @param methodAgruments the list of agruments for this method call */ public void updateClassSetWithNotSimpleMethodCall( String methodCall, List methodAgruments) { + // As this code involves complex string operations, we'll use a method call as an example, + // following its progression through the code. + // Suppose this is our method call: com.example.MyClass.process() + // At this point, our method call become: com.example.MyClass.process String methodCallWithoutParen = methodCall.replace("()", ""); List methodParts = Splitter.onPattern("[.]").splitToList(methodCallWithoutParen); int lengthMethodParts = methodParts.size(); @@ -1309,28 +1313,39 @@ public void updateClassSetWithNotSimpleMethodCall( "Need to check the method call with unsolvedAndNotSimple before using" + " updateClassSetWithNotSimpleMethodCall"); } - String returnTypeClassName = methodParts.get(0); + String returnTypeClassName = toCapital(methodParts.get(0)); String packageName = methodParts.get(0); + // According to the above example, methodName will be process String methodName = methodParts.get(lengthMethodParts - 1); - for (int i = 1; i < lengthMethodParts - 1; i++) { + @SuppressWarnings( + "signature") // this className is from the second-to-last part of a fully-qualified method + // call, which is the simple name of a class. In this case, it is MyClass. + @ClassGetSimpleName String className = methodParts.get(lengthMethodParts - 2); + // After this loop: returnTypeClassName will be ComExample, and packageName will be com.example + for (int i = 1; i < lengthMethodParts - 2; i++) { returnTypeClassName = returnTypeClassName + toCapital(methodParts.get(i)); packageName = packageName + "." + methodParts.get(i); } - returnTypeClassName = returnTypeClassName + toCapital(methodName) + "ReturnType"; - // if the method call is org.package.Class.method(), then the return type of this method will be - // orgPackageClassMethodReturnType, which is a @ClassGetSimpleName + // At this point, returnTypeClassName will be ComExampleMyClassProcessReturnType + returnTypeClassName = + returnTypeClassName + toCapital(className) + toCapital(methodName) + "ReturnType"; + // since returnTypeClassName is just a single long string without any dot in the middle, it will + // be a simple name. @SuppressWarnings("signature") @ClassGetSimpleName String thisReturnType = returnTypeClassName; - UnsolvedClass newClass = new UnsolvedClass(thisReturnType, packageName); + UnsolvedClass returnClass = new UnsolvedClass(thisReturnType, packageName); UnsolvedMethod newMethod = new UnsolvedMethod(methodName, thisReturnType, methodAgruments); - newClass.addMethod(newMethod); - syntheticMethodAndClass.put(newMethod.toString(), newClass); + UnsolvedClass classThatContainMethod = new UnsolvedClass(className, packageName); + newMethod.setStatic(); + classThatContainMethod.addMethod(newMethod); + syntheticMethodAndClass.put(newMethod.toString(), classThatContainMethod); @SuppressWarnings( "signature") // thisReturnType is a @ClassGetSimpleName, so combining it with the // packageName will give us the @FullyQualifiedName @FullyQualifiedName String returnTypeFullName = packageName + "." + thisReturnType; syntheticReturnTypes.add(returnTypeFullName); - this.updateMissingClass(newClass); + this.updateMissingClass(returnClass); + this.updateMissingClass(classThatContainMethod); } /** diff --git a/src/test/resources/unsolvedstaticmethod/expected/com/example/Class.java b/src/test/resources/unsolvedstaticmethod/expected/com/example/Class.java deleted file mode 100644 index f65973071..000000000 --- a/src/test/resources/unsolvedstaticmethod/expected/com/example/Class.java +++ /dev/null @@ -1,8 +0,0 @@ -package com.example; - -public class Class { - - public ProcessReturnType process() { - throw new Error(); - } -} diff --git a/src/test/resources/unsolvedstaticmethod/expected/com/example/ComExampleMyClassProcessReturnType.java b/src/test/resources/unsolvedstaticmethod/expected/com/example/ComExampleMyClassProcessReturnType.java new file mode 100644 index 000000000..d10cda905 --- /dev/null +++ b/src/test/resources/unsolvedstaticmethod/expected/com/example/ComExampleMyClassProcessReturnType.java @@ -0,0 +1,4 @@ +package com.example; + +public class ComExampleMyClassProcessReturnType { +} diff --git a/src/test/resources/unsolvedstaticmethod/expected/com/example/MyClass.java b/src/test/resources/unsolvedstaticmethod/expected/com/example/MyClass.java new file mode 100644 index 000000000..50d989292 --- /dev/null +++ b/src/test/resources/unsolvedstaticmethod/expected/com/example/MyClass.java @@ -0,0 +1,8 @@ +package com.example; + +public class MyClass { + + public static ComExampleMyClassProcessReturnType process() { + throw new Error(); + } +} diff --git a/src/test/resources/unsolvedstaticmethod/expected/com/example/ProcessReturnType.java b/src/test/resources/unsolvedstaticmethod/expected/com/example/ProcessReturnType.java deleted file mode 100644 index 97a4448cb..000000000 --- a/src/test/resources/unsolvedstaticmethod/expected/com/example/ProcessReturnType.java +++ /dev/null @@ -1,4 +0,0 @@ -package com.example; - -public class ProcessReturnType { -} diff --git a/src/test/resources/unsolvedstaticmethod/expected/com/example/Simple.java b/src/test/resources/unsolvedstaticmethod/expected/com/example/Simple.java index d3add7718..e842207ad 100644 --- a/src/test/resources/unsolvedstaticmethod/expected/com/example/Simple.java +++ b/src/test/resources/unsolvedstaticmethod/expected/com/example/Simple.java @@ -1,10 +1,11 @@ package com.example; -import com.example.Class; +import com.example.MyClass; class Simple { void bar() { - Class.process(); + MyClass.process(); + org.testing.ThisClass.process(); } } diff --git a/src/test/resources/unsolvedstaticmethod/expected/org/testing/OrgTestingThisClassProcessReturnType.java b/src/test/resources/unsolvedstaticmethod/expected/org/testing/OrgTestingThisClassProcessReturnType.java new file mode 100644 index 000000000..37a028872 --- /dev/null +++ b/src/test/resources/unsolvedstaticmethod/expected/org/testing/OrgTestingThisClassProcessReturnType.java @@ -0,0 +1,4 @@ +package org.testing; + +public class OrgTestingThisClassProcessReturnType { +} diff --git a/src/test/resources/unsolvedstaticmethod/expected/org/testing/ThisClass.java b/src/test/resources/unsolvedstaticmethod/expected/org/testing/ThisClass.java new file mode 100644 index 000000000..727b66230 --- /dev/null +++ b/src/test/resources/unsolvedstaticmethod/expected/org/testing/ThisClass.java @@ -0,0 +1,8 @@ +package org.testing; + +public class ThisClass { + + public static OrgTestingThisClassProcessReturnType process() { + throw new Error(); + } +} From 27de834c7c815e16876123a1eb650f0e9ff9441a Mon Sep 17 00:00:00 2001 From: unknown Date: Mon, 13 Nov 2023 21:50:00 -0500 Subject: [PATCH 04/10] update test file --- .../unsolvedstaticmethod/input/com/example/Simple.java | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/test/resources/unsolvedstaticmethod/input/com/example/Simple.java b/src/test/resources/unsolvedstaticmethod/input/com/example/Simple.java index d405e510f..e1debf88a 100644 --- a/src/test/resources/unsolvedstaticmethod/input/com/example/Simple.java +++ b/src/test/resources/unsolvedstaticmethod/input/com/example/Simple.java @@ -1,10 +1,11 @@ package com.example; -import com.example.Class; +import com.example.MyClass; class Simple { // Target method. void bar() { - Class.process(); + MyClass.process(); + org.testing.ThisClass.process(); } } From c29821fce8114ab7291c9982e646e378a792767f Mon Sep 17 00:00:00 2001 From: unknown Date: Tue, 14 Nov 2023 12:38:33 -0500 Subject: [PATCH 05/10] add parameters in methods + clarify doc --- .../checkerframework/specimin/UnsolvedSymbolVisitor.java | 4 ++-- .../checkerframework/specimin/UnsolvedStaticMethod.java | 3 +-- .../expected/com/example/MyClass.java | 2 +- .../unsolvedstaticmethod/expected/com/example/Simple.java | 8 ++++++-- .../expected/org/testing/ThisClass.java | 2 +- .../unsolvedstaticmethod/expected/unreal/pack/AClass.java | 8 ++++++++ .../unsolvedstaticmethod/input/com/example/Simple.java | 8 ++++++-- 7 files changed, 25 insertions(+), 10 deletions(-) create mode 100644 src/test/resources/unsolvedstaticmethod/expected/unreal/pack/AClass.java diff --git a/src/main/java/org/checkerframework/specimin/UnsolvedSymbolVisitor.java b/src/main/java/org/checkerframework/specimin/UnsolvedSymbolVisitor.java index b6fcb31d9..15bea2476 100644 --- a/src/main/java/org/checkerframework/specimin/UnsolvedSymbolVisitor.java +++ b/src/main/java/org/checkerframework/specimin/UnsolvedSymbolVisitor.java @@ -1219,7 +1219,7 @@ public static boolean isAClassPath(String potentialClassPath) { * @param fullyName the fully-qualified name of the class * @return the corresponding instance of UnsolvedClass */ - public UnsolvedClass getSimpleSyntheticClassFromFullyQualifiedName( + public static UnsolvedClass getSimpleSyntheticClassFromFullyQualifiedName( @FullyQualifiedName String fullyName) { if (!isAClassPath(fullyName)) { throw new RuntimeException( @@ -1305,7 +1305,7 @@ public void updateClassSetWithNotSimpleMethodCall( // following its progression through the code. // Suppose this is our method call: com.example.MyClass.process() // At this point, our method call become: com.example.MyClass.process - String methodCallWithoutParen = methodCall.replace("()", ""); + String methodCallWithoutParen = methodCall.substring(0, methodCall.indexOf('(')); List methodParts = Splitter.onPattern("[.]").splitToList(methodCallWithoutParen); int lengthMethodParts = methodParts.size(); if (lengthMethodParts <= 2) { diff --git a/src/test/java/org/checkerframework/specimin/UnsolvedStaticMethod.java b/src/test/java/org/checkerframework/specimin/UnsolvedStaticMethod.java index 770dd6048..6ac079ff6 100644 --- a/src/test/java/org/checkerframework/specimin/UnsolvedStaticMethod.java +++ b/src/test/java/org/checkerframework/specimin/UnsolvedStaticMethod.java @@ -4,8 +4,7 @@ import org.junit.Test; /** - * This test checks if Specimin will work for input files that contain unsolved static methods that - * invoked by a simple class name. + * This test checks if Specimin will work for input files that contain unsolved static methods. */ public class UnsolvedStaticMethod { @Test diff --git a/src/test/resources/unsolvedstaticmethod/expected/com/example/MyClass.java b/src/test/resources/unsolvedstaticmethod/expected/com/example/MyClass.java index 50d989292..a14d2e67f 100644 --- a/src/test/resources/unsolvedstaticmethod/expected/com/example/MyClass.java +++ b/src/test/resources/unsolvedstaticmethod/expected/com/example/MyClass.java @@ -2,7 +2,7 @@ public class MyClass { - public static ComExampleMyClassProcessReturnType process() { + public static ComExampleMyClassProcessReturnType process(int parameter0) { throw new Error(); } } diff --git a/src/test/resources/unsolvedstaticmethod/expected/com/example/Simple.java b/src/test/resources/unsolvedstaticmethod/expected/com/example/Simple.java index e842207ad..894304d2d 100644 --- a/src/test/resources/unsolvedstaticmethod/expected/com/example/Simple.java +++ b/src/test/resources/unsolvedstaticmethod/expected/com/example/Simple.java @@ -1,11 +1,15 @@ package com.example; import com.example.MyClass; +import unreal.pack.AClass; class Simple { void bar() { - MyClass.process(); - org.testing.ThisClass.process(); + int x = 5; + String y = 7; + AClass z = new AClass(); + MyClass.process(x); + org.testing.ThisClass.process(y, z); } } diff --git a/src/test/resources/unsolvedstaticmethod/expected/org/testing/ThisClass.java b/src/test/resources/unsolvedstaticmethod/expected/org/testing/ThisClass.java index 727b66230..aa5a1ebb5 100644 --- a/src/test/resources/unsolvedstaticmethod/expected/org/testing/ThisClass.java +++ b/src/test/resources/unsolvedstaticmethod/expected/org/testing/ThisClass.java @@ -2,7 +2,7 @@ public class ThisClass { - public static OrgTestingThisClassProcessReturnType process() { + public static OrgTestingThisClassProcessReturnType process(java.lang.String parameter0, unreal.pack.AClass parameter1) { throw new Error(); } } diff --git a/src/test/resources/unsolvedstaticmethod/expected/unreal/pack/AClass.java b/src/test/resources/unsolvedstaticmethod/expected/unreal/pack/AClass.java new file mode 100644 index 000000000..4edd5640f --- /dev/null +++ b/src/test/resources/unsolvedstaticmethod/expected/unreal/pack/AClass.java @@ -0,0 +1,8 @@ +package unreal.pack; + +public class AClass { + + public AClass() { + throw new Error(); + } +} diff --git a/src/test/resources/unsolvedstaticmethod/input/com/example/Simple.java b/src/test/resources/unsolvedstaticmethod/input/com/example/Simple.java index e1debf88a..fbf460b07 100644 --- a/src/test/resources/unsolvedstaticmethod/input/com/example/Simple.java +++ b/src/test/resources/unsolvedstaticmethod/input/com/example/Simple.java @@ -1,11 +1,15 @@ package com.example; import com.example.MyClass; +import unreal.pack.AClass; class Simple { // Target method. void bar() { - MyClass.process(); - org.testing.ThisClass.process(); + int x = 5; + String y = 7; + AClass z = new AClass(); + MyClass.process(x); + org.testing.ThisClass.process(y, z); } } From a7450242120eb62a975e6ae3060668f57323b271 Mon Sep 17 00:00:00 2001 From: unknown Date: Tue, 14 Nov 2023 12:49:26 -0500 Subject: [PATCH 06/10] a proper String value --- .../unsolvedstaticmethod/expected/com/example/Simple.java | 2 +- .../unsolvedstaticmethod/input/com/example/Simple.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/test/resources/unsolvedstaticmethod/expected/com/example/Simple.java b/src/test/resources/unsolvedstaticmethod/expected/com/example/Simple.java index 894304d2d..886f874ff 100644 --- a/src/test/resources/unsolvedstaticmethod/expected/com/example/Simple.java +++ b/src/test/resources/unsolvedstaticmethod/expected/com/example/Simple.java @@ -7,7 +7,7 @@ class Simple { void bar() { int x = 5; - String y = 7; + String y = "hello"; AClass z = new AClass(); MyClass.process(x); org.testing.ThisClass.process(y, z); diff --git a/src/test/resources/unsolvedstaticmethod/input/com/example/Simple.java b/src/test/resources/unsolvedstaticmethod/input/com/example/Simple.java index fbf460b07..a431bedac 100644 --- a/src/test/resources/unsolvedstaticmethod/input/com/example/Simple.java +++ b/src/test/resources/unsolvedstaticmethod/input/com/example/Simple.java @@ -7,7 +7,7 @@ class Simple { // Target method. void bar() { int x = 5; - String y = 7; + String y = "hello"; AClass z = new AClass(); MyClass.process(x); org.testing.ThisClass.process(y, z); From 34044eb9b5664efe195a7a94329c4fddfde49028 Mon Sep 17 00:00:00 2001 From: unknown Date: Tue, 14 Nov 2023 13:43:35 -0500 Subject: [PATCH 07/10] clarify codes --- .../specimin/UnsolvedSymbolVisitor.java | 31 ++++++++++--------- 1 file changed, 16 insertions(+), 15 deletions(-) diff --git a/src/main/java/org/checkerframework/specimin/UnsolvedSymbolVisitor.java b/src/main/java/org/checkerframework/specimin/UnsolvedSymbolVisitor.java index 15bea2476..5e46e32aa 100644 --- a/src/main/java/org/checkerframework/specimin/UnsolvedSymbolVisitor.java +++ b/src/main/java/org/checkerframework/specimin/UnsolvedSymbolVisitor.java @@ -522,21 +522,22 @@ public Visitable visit(MethodCallExpr method, Void p) { if (!canSolveParameters(method)) { return super.visit(method, p); } - if (unsolvedAndNotSimple(method)) { - updateClassSetWithNotSimpleMethodCall(method.toString(), getArgumentsFromMethodCall(method)); + if (isAnUnsolvedStaticMethodCalledByAQualifiedClassName(method)) { + updateClassSetWithQualifiedStaticMethodCall( + method.toString(), getArgumentsFromMethodCall(method)); } else if (calledByAnIncompleteSyntheticClass(method)) { @ClassGetSimpleName String incompleteClassName = getSyntheticClass(method); updateUnsolvedClassWithMethod(method, incompleteClassName, ""); } else if (unsolvedAndCalledByASimpleClassName(method)) { String methodFullyQualifiedCall = toFullyQualifiedCall(method); - updateClassSetWithNotSimpleMethodCall( + updateClassSetWithQualifiedStaticMethodCall( methodFullyQualifiedCall, getArgumentsFromMethodCall(method)); } this.gotException = calledByAnUnsolvedSymbol(method) || calledByAnIncompleteSyntheticClass(method) - || unsolvedAndNotSimple(method); + || isAnUnsolvedStaticMethodCalledByAQualifiedClassName(method); return super.visit(method, p); } @@ -1269,16 +1270,15 @@ public String toFullyQualifiedCall(MethodCallExpr method) { } /** - * This method checks if a method call is not-simple and unsolved. In this context, we declare a - * not-simple method call as a method that is directly called by a qualified class name. For - * example, for this call org.package.Class.methodFirst().methodSecond(), + * This method checks if a method call is static method that is called by a qualified class name. + * For example, for this call org.package.Class.methodFirst().methodSecond(), * "org.package.Class.methodFirst()" is a not-simple method call, but * "org.package.Class.methodFirst().methodSecond()" is a simple one. * * @param method the method call to be checked * @return true if the method call is not simple and unsolved */ - public boolean unsolvedAndNotSimple(MethodCallExpr method) { + public boolean isAnUnsolvedStaticMethodCalledByAQualifiedClassName(MethodCallExpr method) { try { method.resolve().getReturnType(); return false; @@ -1293,14 +1293,15 @@ public boolean unsolvedAndNotSimple(MethodCallExpr method) { } /** - * For a method call that is not simple, this method will take that method as input and create - * corresponding synthetic class. + * Creates a synthetic class corresponding to a static method called by a qualified class name. + * Ensure to check with {@link #isAnUnsolvedStaticMethodCalledByAQualifiedClassName} before + * calling this method. * - * @param methodCall the method call to be taken as input - * @param methodAgruments the list of agruments for this method call + * @param methodCall the method call to be used as input + * @param methodArguments the list of arguments for this method call */ - public void updateClassSetWithNotSimpleMethodCall( - String methodCall, List methodAgruments) { + public void updateClassSetWithQualifiedStaticMethodCall( + String methodCall, List methodArguments) { // As this code involves complex string operations, we'll use a method call as an example, // following its progression through the code. // Suppose this is our method call: com.example.MyClass.process() @@ -1334,7 +1335,7 @@ public void updateClassSetWithNotSimpleMethodCall( @SuppressWarnings("signature") @ClassGetSimpleName String thisReturnType = returnTypeClassName; UnsolvedClass returnClass = new UnsolvedClass(thisReturnType, packageName); - UnsolvedMethod newMethod = new UnsolvedMethod(methodName, thisReturnType, methodAgruments); + UnsolvedMethod newMethod = new UnsolvedMethod(methodName, thisReturnType, methodArguments); UnsolvedClass classThatContainMethod = new UnsolvedClass(className, packageName); newMethod.setStatic(); classThatContainMethod.addMethod(newMethod); From c51daf38c9776ffab1c70571889b658137006544 Mon Sep 17 00:00:00 2001 From: unknown Date: Tue, 14 Nov 2023 13:56:25 -0500 Subject: [PATCH 08/10] clarify documentation --- .../checkerframework/specimin/UnsolvedSymbolVisitor.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/main/java/org/checkerframework/specimin/UnsolvedSymbolVisitor.java b/src/main/java/org/checkerframework/specimin/UnsolvedSymbolVisitor.java index 5e46e32aa..97c8daa1c 100644 --- a/src/main/java/org/checkerframework/specimin/UnsolvedSymbolVisitor.java +++ b/src/main/java/org/checkerframework/specimin/UnsolvedSymbolVisitor.java @@ -1271,9 +1271,9 @@ public String toFullyQualifiedCall(MethodCallExpr method) { /** * This method checks if a method call is static method that is called by a qualified class name. - * For example, for this call org.package.Class.methodFirst().methodSecond(), - * "org.package.Class.methodFirst()" is a not-simple method call, but - * "org.package.Class.methodFirst().methodSecond()" is a simple one. + * For example, for this call org.package.Class.methodFirst().methodSecond(), this method will + * return true for "org.package.Class.methodFirst()", but not for + * "org.package.Class.methodFirst().methodSecond()". * * @param method the method call to be checked * @return true if the method call is not simple and unsolved From e2e666f42fe6ee3f3fc79ecd7fc5bd931f58e436 Mon Sep 17 00:00:00 2001 From: unknown Date: Tue, 14 Nov 2023 14:02:04 -0500 Subject: [PATCH 09/10] update error message --- .../org/checkerframework/specimin/UnsolvedSymbolVisitor.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/org/checkerframework/specimin/UnsolvedSymbolVisitor.java b/src/main/java/org/checkerframework/specimin/UnsolvedSymbolVisitor.java index 97c8daa1c..a874504ef 100644 --- a/src/main/java/org/checkerframework/specimin/UnsolvedSymbolVisitor.java +++ b/src/main/java/org/checkerframework/specimin/UnsolvedSymbolVisitor.java @@ -1312,7 +1312,7 @@ public void updateClassSetWithQualifiedStaticMethodCall( if (lengthMethodParts <= 2) { throw new RuntimeException( "Need to check the method call with unsolvedAndNotSimple before using" - + " updateClassSetWithNotSimpleMethodCall"); + + " isAnUnsolvedStaticMethodCalledByAQualifiedClassName"); } String returnTypeClassName = toCapital(methodParts.get(0)); String packageName = methodParts.get(0); From 289fdfbd305135da9231a819973a9db17010a42f Mon Sep 17 00:00:00 2001 From: unknown Date: Tue, 14 Nov 2023 21:01:24 -0500 Subject: [PATCH 10/10] update Javadoc --- .../org/checkerframework/specimin/UnsolvedSymbolVisitor.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/main/java/org/checkerframework/specimin/UnsolvedSymbolVisitor.java b/src/main/java/org/checkerframework/specimin/UnsolvedSymbolVisitor.java index a874504ef..367ee8ef4 100644 --- a/src/main/java/org/checkerframework/specimin/UnsolvedSymbolVisitor.java +++ b/src/main/java/org/checkerframework/specimin/UnsolvedSymbolVisitor.java @@ -1297,7 +1297,9 @@ public boolean isAnUnsolvedStaticMethodCalledByAQualifiedClassName(MethodCallExp * Ensure to check with {@link #isAnUnsolvedStaticMethodCalledByAQualifiedClassName} before * calling this method. * - * @param methodCall the method call to be used as input + * @param methodCall the method call to be used as input. This method call must contain one or + * more dot separated identifiers, followed by a single pair of parentheses containing + * arguments * @param methodArguments the list of arguments for this method call */ public void updateClassSetWithQualifiedStaticMethodCall(