Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add static method #42

Merged
merged 10 commits into from
Nov 15, 2023
Merged
Show file tree
Hide file tree
Changes from 9 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
172 changes: 114 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,60 @@ 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
LoiNguyenCS marked this conversation as resolved.
Show resolved Hide resolved
* @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
LoiNguyenCS marked this conversation as resolved.
Show resolved Hide resolved
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