Skip to content

Commit

Permalink
Merge pull request #42 from LoiNguyenCS/add-static-method
Browse files Browse the repository at this point in the history
Add static method
  • Loading branch information
LoiNguyenCS authored Nov 15, 2023
2 parents 9ec1253 + 289fdfb commit 40572c2
Show file tree
Hide file tree
Showing 14 changed files with 213 additions and 80 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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);
}
Expand Down
13 changes: 13 additions & 0 deletions src/main/java/org/checkerframework/specimin/UnsolvedMethod.java
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,9 @@ public class UnsolvedMethod {
*/
private List<String> parameterList;

/** This field is set to true if this method is a static method */
private boolean isStatic = false;

/**
* Create an instance of UnsolvedMethod
*
Expand Down Expand Up @@ -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.
*
Expand All @@ -85,7 +93,12 @@ public String toString() {
if (!returnType.equals("")) {
returnTypeInString = returnType + " ";
}
String staticField = "";
if (isStatic) {
staticField = "static ";
}
return "\n public "
+ staticField
+ returnTypeInString
+ name
+ "("
Expand Down
174 changes: 116 additions & 58 deletions src/main/java/org/checkerframework/specimin/UnsolvedSymbolVisitor.java
Original file line number Diff line number Diff line change
Expand Up @@ -130,12 +130,6 @@ public class UnsolvedSymbolVisitor extends ModifierVisitor<Void> {
/** This map the classes in the compilation unit with the related package */
private final Map<String, String> 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<>();

Expand Down Expand Up @@ -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);
}
Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -530,16 +522,22 @@ public Visitable visit(MethodCallExpr method, Void p) {
if (!canSolveParameters(method)) {
return super.visit(method, p);
}
if (unsolvedAndNotSimple(method)) {
updateClassSetWithNotSimpleMethodCall(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);
updateClassSetWithQualifiedStaticMethodCall(
methodFullyQualifiedCall, getArgumentsFromMethodCall(method));
}

this.gotException =
calledByAnUnsolvedSymbol(method)
|| calledByAnIncompleteSyntheticClass(method)
|| unsolvedAndNotSimple(method);
|| isAnUnsolvedStaticMethodCalledByAQualifiedClassName(method);
return super.visit(method, p);
}

Expand All @@ -562,10 +560,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;
Expand All @@ -589,10 +589,14 @@ public Visitable visit(ObjectCreationExpr newExpr, Void p) {
try {
List<String> 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
}
Expand Down Expand Up @@ -686,9 +690,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);
Expand Down Expand Up @@ -727,7 +732,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!");
Expand Down Expand Up @@ -1131,8 +1136,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);
Expand Down Expand Up @@ -1229,16 +1233,52 @@ public static UnsolvedClass getSimpleSyntheticClassFromFullyQualifiedName(
}

/**
* 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(),
* "org.package.Class.methodFirst()" is a not-simple method call, but
* "org.package.Class.methodFirst().methodSecond()" is a simple one.
* 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<Expression> 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 static method that is called by a qualified class name.
* 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
*/
public static boolean unsolvedAndNotSimple(MethodCallExpr method) {
public boolean isAnUnsolvedStaticMethodCalledByAQualifiedClassName(MethodCallExpr method) {
try {
method.resolve().getReturnType();
return false;
Expand All @@ -1253,44 +1293,62 @@ 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
* 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 method the method call to be taken 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 updateClassSetWithNotSimpleMethodCall(MethodCallExpr method) {
String methodCall = method.toString();
String methodCallWithoutParen = methodCall.replace("()", "");
public void updateClassSetWithQualifiedStaticMethodCall(
String methodCall, List<String> 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()
// At this point, our method call become: com.example.MyClass.process
String methodCallWithoutParen = methodCall.substring(0, methodCall.indexOf('('));
List<String> methodParts = Splitter.onPattern("[.]").splitToList(methodCallWithoutParen);
int lengthMethodParts = methodParts.size();
if (lengthMethodParts <= 2) {
throw new RuntimeException(
"Need to check the method call with unsolvedAndNotSimple before using"
+ " updateClassSetWithNotSimpleMethodCall");
+ " isAnUnsolvedStaticMethodCalledByAQualifiedClassName");
}
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);
UnsolvedMethod newMethod =
new UnsolvedMethod(methodName, thisReturnType, getArgumentsFromMethodCall(method));
newClass.addMethod(newMethod);
syntheticMethodAndClass.put(newMethod.toString(), newClass);
UnsolvedClass returnClass = new UnsolvedClass(thisReturnType, packageName);
UnsolvedMethod newMethod = new UnsolvedMethod(methodName, thisReturnType, methodArguments);
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);
}

/**
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
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.
*/
public class UnsolvedStaticMethod {
@Test
public void runTest() throws IOException {
SpeciminTestExecutor.runTestWithoutJarPaths(
"unsolvedstaticmethod",
new String[] {"com/example/Simple.java"},
new String[] {"com.example.Simple#bar()"});
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
package com.example;

public class ComExampleMyClassProcessReturnType {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package com.example;

public class MyClass {

public static ComExampleMyClassProcessReturnType process(int parameter0) {
throw new Error();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package com.example;

import com.example.MyClass;
import unreal.pack.AClass;

class Simple {

void bar() {
int x = 5;
String y = "hello";
AClass z = new AClass();
MyClass.process(x);
org.testing.ThisClass.process(y, z);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
package org.testing;

public class OrgTestingThisClassProcessReturnType {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package org.testing;

public class ThisClass {

public static OrgTestingThisClassProcessReturnType process(java.lang.String parameter0, unreal.pack.AClass parameter1) {
throw new Error();
}
}
Loading

0 comments on commit 40572c2

Please sign in to comment.