Skip to content

Commit

Permalink
Merge pull request #10 from LoiNguyenCS/main
Browse files Browse the repository at this point in the history
update newest update from main
  • Loading branch information
LoiNguyenCS authored Nov 27, 2023
2 parents 2ffc4d6 + 72d9f6c commit 620764b
Show file tree
Hide file tree
Showing 17 changed files with 201 additions and 60 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/gradle.yml
Original file line number Diff line number Diff line change
Expand Up @@ -24,4 +24,4 @@ jobs:
- name: Setup Gradle
uses: gradle/gradle-build-action@v2
- name: Run build with Gradle Wrapper
run: ./gradlew build
run: ./gradlew build expectedTestOutputsMustCompile
5 changes: 5 additions & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,11 @@ task requireJavadoc(type: JavaExec) {
args "src/main/java"
}

task expectedTestOutputsMustCompile(type: Exec) {
// TODO: should this task run in CI, or as part of the regular tests?
commandLine "sh", "typecheck_test_outputs.sh"
}

checkerFramework {
checkers = [
'org.checkerframework.checker.nullness.NullnessChecker',
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
package org.checkerframework.specimin;

import com.github.javaparser.ast.body.ClassOrInterfaceDeclaration;
import com.github.javaparser.ast.type.ClassOrInterfaceType;
import com.github.javaparser.ast.visitor.ModifierVisitor;
import com.github.javaparser.ast.visitor.Visitable;
import com.github.javaparser.resolution.UnsolvedSymbolException;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;

/**
* A visitor that traverses a Java file's AST and creates a map, associating the file name with the
* set of types used inside it.
*/
public class GetTypesFullNameVisitor extends ModifierVisitor<Void> {

/** The directory path of the Java file. */
private String fileDirectory = "";

/**
* A map that associates the file directory with the set of fully qualified names of types used
* within that file.
*/
private Map<String, Set<String>> fileAndAssociatedTypes = new HashMap<>();

/**
* Get the map of files' directories and types used within those files.
*
* @return the value of fileAndAssociatedTypes
*/
public Map<String, Set<String>> getFileAndAssociatedTypes() {
return Collections.unmodifiableMap(fileAndAssociatedTypes);
}

@Override
public Visitable visit(ClassOrInterfaceDeclaration decl, Void p) {
// Nested type classes don't have a separate class file.
if (!decl.isNestedType()) {
fileDirectory = decl.getFullyQualifiedName().get().replace(".", "/") + ".java";
fileAndAssociatedTypes.put(fileDirectory, new HashSet<>());
}
return super.visit(decl, p);
}

@Override
public Visitable visit(ClassOrInterfaceType type, Void p) {
String typeFullName;
try {
typeFullName = type.resolve().getQualifiedName();
} catch (UnsolvedSymbolException e) {
return super.visit(type, p);
}
if (fileAndAssociatedTypes.containsKey(fileDirectory)) {
fileAndAssociatedTypes.get(fileDirectory).add(typeFullName);
return super.visit(type, p);
} else {
throw new RuntimeException(
"Unexpected files and types: " + fileDirectory + ", " + typeFullName);
}
}
}
73 changes: 41 additions & 32 deletions src/main/java/org/checkerframework/specimin/JavaTypeCorrect.java
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,6 @@
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.checkerframework.checker.signature.qual.ClassGetSimpleName;
import org.checkerframework.checker.signature.qual.DotSeparatedIdentifiers;

/**
* This class uses javac to analyze files. If there are any incompatible type errors in those files,
Expand All @@ -34,7 +32,13 @@ class JavaTypeCorrect {
* This map is for type correcting. The key is the name of the current incorrect type, and the
* value is the name of the desired correct type.
*/
private Map<@ClassGetSimpleName String, @ClassGetSimpleName String> typeToChange;
private Map<String, String> typeToChange;

/**
* A map that associates the file directory with the set of fully qualified names of types used
* within that file.
*/
private Map<String, Set<String>> fileAndAssociatedTypes = new HashMap<>();

/**
* Create a new JavaTypeCorrect instance. The directories of files in fileNameList are relative to
Expand All @@ -43,18 +47,22 @@ class JavaTypeCorrect {
* @param rootDirectory the root directory of the files to correct types
* @param fileNameList the list of the relative directory of the files to correct types
*/
public JavaTypeCorrect(String rootDirectory, Set<String> fileNameList) {
public JavaTypeCorrect(
String rootDirectory,
Set<String> fileNameList,
Map<String, Set<String>> fileAndAssociatedTypes) {
this.fileNameList = fileNameList;
this.sourcePath = new File(rootDirectory).getAbsolutePath();
this.typeToChange = new HashMap<>();
this.fileAndAssociatedTypes = fileAndAssociatedTypes;
}

/**
* Get the value of typeToChange
*
* @return the value of typeToChange
*/
public Map<@ClassGetSimpleName String, @ClassGetSimpleName String> getTypeToChange() {
public Map<String, String> getTypeToChange() {
return typeToChange;
}

Expand Down Expand Up @@ -87,7 +95,7 @@ public void runJavacAndUpdateTypes(String filePath) {
String line;
while ((line = reader.readLine()) != null) {
if (line.contains("error: incompatible types")) {
updateTypeToChange(line);
updateTypeToChange(line, filePath);
}
}
} catch (Exception e) {
Expand All @@ -99,8 +107,9 @@ public void runJavacAndUpdateTypes(String filePath) {
* This method updates typeToChange by relying on the error messages from javac
*
* @param errorMessage the error message to be analyzed
* @param filePath the path of the file where this error happens
*/
private void updateTypeToChange(String errorMessage) {
private void updateTypeToChange(String errorMessage, String filePath) {
List<String> splitErrorMessage = Splitter.onPattern("\\s+").splitToList(errorMessage);
if (splitErrorMessage.size() < 7) {
throw new RuntimeException("Unexpected type error messages: " + errorMessage);
Expand All @@ -110,38 +119,38 @@ private void updateTypeToChange(String errorMessage) {
* 2. error: incompatible types: found <type1> required <type2>
*/
if (errorMessage.contains("cannot be converted to")) {
// since this is from javac, we know that these will be dot-separated identifiers.
@SuppressWarnings("signature")
@DotSeparatedIdentifiers String incorrectType = splitErrorMessage.get(4);
@SuppressWarnings("signature")
@DotSeparatedIdentifiers String correctType = splitErrorMessage.get(splitErrorMessage.size() - 1);
typeToChange.put(toSimpleName(incorrectType), toSimpleName(correctType));
String incorrectType = splitErrorMessage.get(4);
String correctType = splitErrorMessage.get(splitErrorMessage.size() - 1);
typeToChange.put(incorrectType, tryResolveFullyQualifiedType(correctType, filePath));
} else {
@SuppressWarnings("signature")
@DotSeparatedIdentifiers String incorrectType = splitErrorMessage.get(5);
@SuppressWarnings("signature")
@DotSeparatedIdentifiers String correctType = splitErrorMessage.get(splitErrorMessage.size() - 1);
typeToChange.put(toSimpleName(incorrectType), toSimpleName(correctType));
String incorrectType = splitErrorMessage.get(5);
String correctType = splitErrorMessage.get(splitErrorMessage.size() - 1);
typeToChange.put(incorrectType, tryResolveFullyQualifiedType(correctType, filePath));
}
}

/**
* This method takes the name of a class and converts it to the @ClassGetSimpleName type according
* to Checker Framework. If the name is already in the @ClassGetSimpleName form, this method will
* not make any changes
* This method tries to get the fully-qualified name of a type based on the simple name of that
* type and the class file where that type is used.
*
* @param className the name of the class to be converted
* @return the simple name of the class
* @param type the type to be taken as input
* @param filePath the path of the file where type is used
* @return the fully-qualified name of that type if any. Otherwise, return the original expression
* of type.
*/
// the code is self-explanatory, essentially the last element of a class name is the simple name
// of that class. This method takes the input from the error message of javac, so we know that
// className will be a dot-separated identifier.
@SuppressWarnings("signature")
public static @ClassGetSimpleName String toSimpleName(@DotSeparatedIdentifiers String className) {
List<String> classNameParts = Splitter.onPattern("[.]").splitToList(className);
if (classNameParts.size() < 2) {
return className;
public String tryResolveFullyQualifiedType(String type, String filePath) {
// type is already in the fully qualifed format
if (Splitter.onPattern("\\.").splitToList(type).size() > 1) {
return type;
}
if (fileAndAssociatedTypes.containsKey(filePath)) {
Set<String> fullyQualifiedType = fileAndAssociatedTypes.get(filePath);
for (String typeFullName : fullyQualifiedType) {
if (typeFullName.substring(typeFullName.lastIndexOf(".") + 1).equals(type)) {
return typeFullName;
}
}
}
return classNameParts.get(classNameParts.size() - 1);
return type;
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package org.checkerframework.specimin;

import com.github.javaparser.StaticJavaParser;
import com.github.javaparser.ast.ImportDeclaration;
import com.github.javaparser.ast.Node;
import com.github.javaparser.ast.body.ConstructorDeclaration;
import com.github.javaparser.ast.body.MethodDeclaration;
import com.github.javaparser.ast.visitor.ModifierVisitor;
Expand Down Expand Up @@ -28,6 +30,13 @@ public class MethodPrunerVisitor extends ModifierVisitor<Void> {
*/
private Set<String> methodsToEmpty;

/**
* This is the set of classes used by the target methods. We use this set to determine if we
* should keep or delete an import statement. The strings representing the classes are in
* the @FullyQualifiedName form.
*/
private Set<String> classesUsedByTargetMethods;

/**
* This boolean tracks whether the element currently being visited is inside a target method. It
* is set by {@link #visit(MethodDeclaration, Void)}.
Expand All @@ -42,10 +51,25 @@ public class MethodPrunerVisitor extends ModifierVisitor<Void> {
* @param methodsToKeep the set of methods whose bodies should be kept intact (usually the target
* methods for specimin)
* @param methodsToEmpty the set of methods whose bodies should be removed
* @param classesUsedByTargetMethods the classes used by target methods
*/
public MethodPrunerVisitor(Set<String> methodsToKeep, Set<String> methodsToEmpty) {
public MethodPrunerVisitor(
Set<String> methodsToKeep,
Set<String> methodsToEmpty,
Set<String> classesUsedByTargetMethods) {
this.methodsToLeaveUnchanged = methodsToKeep;
this.methodsToEmpty = methodsToEmpty;
this.classesUsedByTargetMethods = classesUsedByTargetMethods;
}

@Override
public Node visit(ImportDeclaration decl, Void p) {
String classFullName = decl.getNameAsString();
if (classesUsedByTargetMethods.contains(classFullName)) {
return super.visit(decl, p);
}
decl.remove();
return decl;
}

@Override
Expand Down
12 changes: 10 additions & 2 deletions src/main/java/org/checkerframework/specimin/SpeciminRunner.java
Original file line number Diff line number Diff line change
Expand Up @@ -164,8 +164,15 @@ public static void performMinimization(
relatedClass.add(directoryOfFile);
}
}
GetTypesFullNameVisitor getTypesFullNameVisitor = new GetTypesFullNameVisitor();
for (CompilationUnit cu : parsedTargetFiles.values()) {
cu.accept(getTypesFullNameVisitor, null);
}
Map<String, Set<String>> filesAndAssociatedTypes =
getTypesFullNameVisitor.getFileAndAssociatedTypes();
// correct the types of all related files before adding them to parsedTargetFiles
JavaTypeCorrect typeCorrecter = new JavaTypeCorrect(root, relatedClass);
JavaTypeCorrect typeCorrecter =
new JavaTypeCorrect(root, relatedClass, filesAndAssociatedTypes);
typeCorrecter.correctTypesForAllFiles();
Map<@ClassGetSimpleName String, @ClassGetSimpleName String> typesToChange =
typeCorrecter.getTypeToChange();
Expand All @@ -180,7 +187,8 @@ public static void performMinimization(
}

MethodPrunerVisitor methodPruner =
new MethodPrunerVisitor(finder.getTargetMethods(), finder.getUsedMembers());
new MethodPrunerVisitor(
finder.getTargetMethods(), finder.getUsedMembers(), finder.getUsedClass());

for (CompilationUnit cu : parsedTargetFiles.values()) {
cu.accept(methodPruner, null);
Expand Down
22 changes: 14 additions & 8 deletions src/main/java/org/checkerframework/specimin/UnsolvedClass.java
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import com.google.common.base.Splitter;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;
import org.checkerframework.checker.signature.qual.ClassGetSimpleName;
Expand All @@ -12,14 +13,20 @@
* The reason is that the class file is not in the root directory.
*/
public class UnsolvedClass {
/** Set of methods belongs to the class */
private final Set<UnsolvedMethod> methods;
/**
* Set of methods belongs to the class. Must be a linked set to ensure deterministic iteration
* order when writing files synthetic classes.
*/
private final LinkedHashSet<UnsolvedMethod> methods;

/** The name of the class */
private final @ClassGetSimpleName String className;

/** The fields of this class */
private final Set<String> classFields;
/**
* The fields of this class. Must be a linked set to ensure deterministic iteration order when
* writing files for synthetic classes.
*/
private final LinkedHashSet<String> classFields;

/**
* The name of the package of the class. We rely on the import statements from the source codes to
Expand All @@ -38,9 +45,9 @@ public class UnsolvedClass {
*/
public UnsolvedClass(@ClassGetSimpleName String className, String packageName) {
this.className = className;
this.methods = new HashSet<>();
this.methods = new LinkedHashSet<>();
this.packageName = packageName;
this.classFields = new HashSet<>();
this.classFields = new LinkedHashSet<>();
}

/**
Expand Down Expand Up @@ -119,8 +126,7 @@ public int getNumberOfTypeVariables() {
* @param currentReturnType the current return type of this method
* @param desiredReturnType the new return type
*/
public void updateMethodByReturnType(
@ClassGetSimpleName String currentReturnType, @ClassGetSimpleName String desiredReturnType) {
public void updateMethodByReturnType(String currentReturnType, String desiredReturnType) {
for (UnsolvedMethod method : methods) {
if (method.getReturnType().equals(currentReturnType)) {
method.setReturnType(desiredReturnType);
Expand Down
10 changes: 4 additions & 6 deletions src/main/java/org/checkerframework/specimin/UnsolvedMethod.java
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
package org.checkerframework.specimin;

import java.util.List;
import org.checkerframework.checker.signature.qual.ClassGetSimpleName;

/**
* An UnsolvedMethod instance is a representation of a method that can not be solved by
Expand All @@ -15,7 +14,7 @@ public class UnsolvedMethod {
* The return type of the method. At the moment, we set the return type the same as the class
* where the method belongs to.
*/
private @ClassGetSimpleName String returnType;
private String returnType;

/**
* The list of parameters of the method. (Right now we won't touch it until the new variant of
Expand All @@ -33,8 +32,7 @@ public class UnsolvedMethod {
* @param returnType the return type of the method
* @param parameterList the list of parameters for this method
*/
public UnsolvedMethod(
String name, @ClassGetSimpleName String returnType, List<String> parameterList) {
public UnsolvedMethod(String name, String returnType, List<String> parameterList) {
this.name = name;
this.returnType = returnType;
this.parameterList = parameterList;
Expand All @@ -46,7 +44,7 @@ public UnsolvedMethod(
*
* @param returnType the return type to bet set for this method
*/
public void setReturnType(@ClassGetSimpleName String returnType) {
public void setReturnType(String returnType) {
this.returnType = returnType;
}

Expand All @@ -55,7 +53,7 @@ public void setReturnType(@ClassGetSimpleName String returnType) {
*
* @return the value of returnType
*/
public @ClassGetSimpleName String getReturnType() {
public String getReturnType() {
return returnType;
}

Expand Down
Loading

0 comments on commit 620764b

Please sign in to comment.