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 codes for tricky parameters #46

Merged
merged 25 commits into from
Nov 27, 2023
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
20332f8
add codes for tricky páºáºáºarameters
LoiNguyenCS Nov 16, 2023
ec4807f
codes to handle parameters
LoiNguyenCS Nov 16, 2023
348aaa5
remove debugging line
LoiNguyenCS Nov 16, 2023
bb163cc
remove < in javadoc
LoiNguyenCS Nov 16, 2023
3801117
remove unused method
LoiNguyenCS Nov 16, 2023
95968ff
re-add anonymousclass
LoiNguyenCS Nov 16, 2023
0c4b7af
codes and test cases for multi type variables
LoiNguyenCS Nov 19, 2023
7666b1b
some cleaning
LoiNguyenCS Nov 19, 2023
e601238
add throw
LoiNguyenCS Nov 19, 2023
e2188c2
some more cleaning and clarification
LoiNguyenCS Nov 19, 2023
e130de3
checkpoint: address all but one cr comment from myself
kelloggm Nov 20, 2023
3c414c2
add test for lower case class name
kelloggm Nov 20, 2023
7d6261b
fix expected test output
kelloggm Nov 20, 2023
014c1dd
checkpoint
kelloggm Nov 20, 2023
4cd794b
checkpoint, no longer crashing or infinite looping
kelloggm Nov 21, 2023
bbc59fb
handle simple type variables
kelloggm Nov 21, 2023
7ad4b83
cleanup to prep for review
kelloggm Nov 21, 2023
0254a20
remove debug code
kelloggm Nov 21, 2023
89d47ff
failing test
kelloggm Nov 22, 2023
2ffc4d6
address CR comments
kelloggm Nov 22, 2023
620764b
Merge pull request #10 from LoiNguyenCS/main
LoiNguyenCS Nov 27, 2023
5acffa5
remove annotations
LoiNguyenCS Nov 27, 2023
0ea0c7c
add exception for type variables
LoiNguyenCS Nov 27, 2023
78e201f
restore order of expected file in AnonymousClass
LoiNguyenCS Nov 27, 2023
e08e674
Merge pull request #9 from kelloggm/typevar-collision
LoiNguyenCS Nov 27, 2023
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
20 changes: 18 additions & 2 deletions src/main/java/org/checkerframework/specimin/SpeciminRunner.java
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
import joptsimple.OptionParser;
import joptsimple.OptionSet;
import joptsimple.OptionSpec;
import org.checkerframework.checker.signature.qual.ClassGetSimpleName;

/** This class is the main runner for Specimin. Use its main() method to start Specimin. */
public class SpeciminRunner {
Expand Down Expand Up @@ -165,7 +166,9 @@ public static void performMinimization(
// correct the types of all related files before adding them to parsedTargetFiles
JavaTypeCorrect typeCorrecter = new JavaTypeCorrect(root, relatedClass);
typeCorrecter.correctTypesForAllFiles();
addMissingClass.updateTypes(typeCorrecter.getTypeToChange());
Map<@ClassGetSimpleName String, @ClassGetSimpleName String> typeToChange =
LoiNguyenCS marked this conversation as resolved.
Show resolved Hide resolved
typeCorrecter.getTypeToChange();
addMissingClass.updateTypes(typeToChange);

for (String directory : relatedClass) {
// directories already in parsedTargetFiles are original files in the root directory, we are
Expand All @@ -187,7 +190,20 @@ public static void performMinimization(
if (isEmptyCompilationUnit(target.getValue())) {
// target key will have this form: "path/of/package/ClassName.java"
String classFullyQualfiedName = target.getKey().replace("/", ".");
classFullyQualfiedName = classFullyQualfiedName.replace(".java", "");
// this condition is actually always true
LoiNguyenCS marked this conversation as resolved.
Show resolved Hide resolved
if (classFullyQualfiedName.endsWith(".java")) {
classFullyQualfiedName =
classFullyQualfiedName.substring(0, classFullyQualfiedName.length() - 5);
}
@SuppressWarnings("signature") // since it's the last element of a fully qualified path
@ClassGetSimpleName String simpleName =
classFullyQualfiedName.substring(classFullyQualfiedName.lastIndexOf(".") + 1);
// If this condition is true, this class is a synthetic class initially created to be a
// return type of some synthetic methods, but later javac has found the correct return type
// for that method.
if (typeToChange.containsKey(simpleName)) {
continue;
}
if (!finder.getUsedClass().contains(classFullyQualfiedName)) {
continue;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import com.github.javaparser.ast.body.ClassOrInterfaceDeclaration;
import com.github.javaparser.ast.body.ConstructorDeclaration;
import com.github.javaparser.ast.body.MethodDeclaration;
import com.github.javaparser.ast.body.Parameter;
import com.github.javaparser.ast.body.VariableDeclarator;
import com.github.javaparser.ast.expr.Expression;
import com.github.javaparser.ast.expr.FieldAccessExpr;
Expand Down Expand Up @@ -207,6 +208,21 @@ public Visitable visit(MethodDeclaration method, Void p) {
return result;
}

@Override
public Visitable visit(Parameter para, Void p) {
if (insideTargetMethod) {
ResolvedType paraType = para.resolve().getType();
if (paraType.isReferenceType()) {
String paraName = paraType.asReferenceType().getTypeDeclaration().get().getQualifiedName();
LoiNguyenCS marked this conversation as resolved.
Show resolved Hide resolved
usedClass.add(paraName);
for (ResolvedType parameter : paraType.asReferenceType().typeParametersValues()) {
usedClass.add(parameter.describe());
}
}
}
return super.visit(para, p);
}

@Override
public Visitable visit(MethodCallExpr call, Void p) {
if (insideTargetMethod) {
Expand Down
22 changes: 21 additions & 1 deletion src/main/java/org/checkerframework/specimin/UnsolvedClass.java
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,9 @@ public class UnsolvedClass {
*/
private final String packageName;

/** This field checks if this class has a placeholder */
private boolean hasAPlaceHolder = false;
LoiNguyenCS marked this conversation as resolved.
Show resolved Hide resolved

/**
* Create an instance of UnsolvedClass
*
Expand Down Expand Up @@ -95,6 +98,19 @@ public void addFields(String variableExpression) {
this.classFields.add(variableExpression);
}

/** This method sets hasAPlaceHolder to true */
public void setPlaceHolder() {
this.hasAPlaceHolder = true;
}

/**
* This method tells if the current class has a placeholder.
*
* @return true if the current class has a place holder.
*/
public boolean hasPlaceHolder() {
return this.hasAPlaceHolder;
}
/**
* Update the return type of a method. Note: this method is supposed to be used to update
* synthetic methods, where the return type of each method is distinct.
Expand Down Expand Up @@ -149,7 +165,11 @@ public void updateFieldByType(String currentType, String correctType) {
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append("package ").append(packageName).append(";\n");
sb.append("public class ").append(className).append(" {\n");
if (hasAPlaceHolder) {
LoiNguyenCS marked this conversation as resolved.
Show resolved Hide resolved
sb.append("public class ").append(className).append("<T> {\n");
} else {
sb.append("public class ").append(className).append(" {\n");
}
for (String variableDeclarations : classFields) {
sb.append(" " + "public " + variableDeclarations + ";\n");
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
import com.github.javaparser.ast.visitor.Visitable;
import com.github.javaparser.resolution.UnsolvedSymbolException;
import com.github.javaparser.resolution.declarations.ResolvedMethodDeclaration;
import com.github.javaparser.resolution.declarations.ResolvedParameterDeclaration;
import com.github.javaparser.resolution.declarations.ResolvedReferenceTypeDeclaration;
import com.github.javaparser.resolution.types.ResolvedReferenceType;
import com.github.javaparser.resolution.types.ResolvedType;
Expand Down Expand Up @@ -263,6 +264,21 @@ public Visitable visit(PackageDeclaration node, Void arg) {
return super.visit(node, arg);
}

@Override
public Node visit(ImportDeclaration decl, Void arg) {
@SuppressWarnings(
"signature") // since this is the content of an import statement, it will be a qualified
// class path
@FullyQualifiedName String declAsString = decl.getNameAsString();
// if there is no jar paths included, every imported class is unsolved, apart from those
// belonging to the Java language.
if (!classesFromJar.isEmpty() || declAsString.startsWith("java")) {
LoiNguyenCS marked this conversation as resolved.
Show resolved Hide resolved
return super.visit(decl, arg);
}
updateMissingClass(getSimpleSyntheticClassFromFullyQualifiedName(declAsString));
return super.visit(decl, arg);
}

@Override
public Visitable visit(ClassOrInterfaceDeclaration node, Void arg) {
SimpleName nodeName = node.getName();
Expand Down Expand Up @@ -400,13 +416,43 @@ public Visitable visit(BlockStmt node, Void p) {

@Override
public Visitable visit(VariableDeclarator decl, Void p) {
// This part is to update the symbol table.
boolean isAField =
!decl.getParentNode().isEmpty() && (decl.getParentNode().get() instanceof FieldDeclaration);
if (!isAField) {
HashSet<String> currentListOfLocals = localVariables.removeFirst();
currentListOfLocals.add(decl.getNameAsString());
localVariables.addFirst(currentListOfLocals);
}

// This part is to create synthetic class for the type of decl if needed.
Type declType = decl.getType();
try {
declType.resolve();
} catch (UnsolvedSymbolException | UnsupportedOperationException e) {
String typeAsString = declType.asString();
List<String> elements = Splitter.onPattern("\\.").splitToList(typeAsString);
// There could be two cases here: either a fully-qualified class name or a simple class name.
// This is the fully-qualified case.
if (elements.size() > 1) {
@SuppressWarnings("signature") // since this type is in a fully-qualfied form
@FullyQualifiedName String qualifiedTypeName = typeAsString;
updateMissingClass(getSimpleSyntheticClassFromFullyQualifiedName(qualifiedTypeName));
}
/**
* Handles the case where the type is a simple class name. Two sub-cases are considered: 1.
* The class is included among the import statements. 2. The class is not included in the
* import statements but is in the same directory as the input class. The first sub-case is
* addressed by the visit method for ImportDeclaration.
*/
else if (!classAndPackageMap.containsKey(typeAsString)) {
@SuppressWarnings("signature") // since this is the simple name case
@ClassGetSimpleName String className = typeAsString;
String packageName = this.currentPackage;
UnsolvedClass newClass = new UnsolvedClass(className, packageName);
updateMissingClass(newClass);
}
}
return super.visit(decl, p);
}

Expand Down Expand Up @@ -493,7 +539,7 @@ public Visitable visit(MethodDeclaration node, Void arg) {
}
}

HashSet<String> currentLocalVariables = new HashSet<>();
HashSet<String> currentLocalVariables = getParameterFromAMethodDeclaration(node);
localVariables.addFirst(currentLocalVariables);
super.visit(node, arg);
localVariables.removeFirst();
Expand Down Expand Up @@ -544,8 +590,28 @@ public Visitable visit(MethodCallExpr method, Void p) {
@Override
public Visitable visit(Parameter parameter, Void p) {
try {
parameter.resolve().describeType();
return super.visit(parameter, p);
ResolvedParameterDeclaration resolvedParameter = parameter.resolve();
// Specimin will update synthetic class based on import statements before working on
// parameters. Why a parameter could be resolved but not described? Specimin creates the
// synthetic class based on the import statements, so if the synthetic class indeed needs a
// placeholder, Specimin will miss it.
try {
resolvedParameter.describeType();
return super.visit(parameter, p);
} catch (IllegalArgumentException e) {
// following the above explanation, typeName will be in this form path.to.A<path.to.B>
String typeName = parameter.getType().asString();
@SuppressWarnings(
"signature") // since this type is included among the import statements, we can expect
// that when it is used, it will be in its simple form.
@ClassGetSimpleName String typeToAddPlaceHolder = typeName.substring(0, typeName.indexOf("<"));
LoiNguyenCS marked this conversation as resolved.
Show resolved Hide resolved
UnsolvedClass updateClass =
new UnsolvedClass(
typeToAddPlaceHolder,
classAndPackageMap.getOrDefault(typeToAddPlaceHolder, currentPackage));
updateClass.setPlaceHolder();
updateMissingClass(updateClass);
}
}
// If the parameter originates from a Java built-in library, such as java.io or java.lang,
// an UnsupportedOperationException will be thrown instead.
Expand Down Expand Up @@ -823,6 +889,21 @@ public static boolean isASuperCall(Expression node) {
}
}

/**
* Given a method declaration, this method will return the set of parameters of that method
* declaration.
*
* @param decl the method declaration
* @return the set of parameters of decl
*/
public HashSet<String> getParameterFromAMethodDeclaration(MethodDeclaration decl) {
HashSet<String> setOfParameters = new HashSet<>();
for (Parameter parameter : decl.getParameters()) {
setOfParameters.add(parameter.getName().asString());
}
return setOfParameters;
}

/**
* For a super call, this method will update the corresponding synthetic class
*
Expand Down Expand Up @@ -1110,6 +1191,9 @@ public void updateMissingClass(UnsolvedClass missedClass) {
for (String variablesDescription : missedClass.getClassFields()) {
e.addFields(variablesDescription);
}
if (missedClass.hasPlaceHolder()) {
e.setPlaceHolder();
}
return;
}
}
Expand Down
15 changes: 15 additions & 0 deletions src/test/java/org/checkerframework/specimin/UnsolvedParameter.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package org.checkerframework.specimin;

import java.io.IOException;
import org.junit.Test;

/** This test checks if Specimin can work for tricky, unsolved parameters. */
public class UnsolvedParameter {
@Test
public void runTest() throws IOException {
SpeciminTestExecutor.runTestWithoutJarPaths(
"unsolvedparameter",
new String[] {"com/example/Simple.java"},
new String[] {"com.example.Simple#lengthImportStatement(NodeList<ImportDeclaration>)"});
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,11 @@

public class SomeClass {

public SomeClass() {
public int getLocalVar() {
throw new Error();
}

public int getLocalVar() {
public SomeClass() {
throw new Error();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package com.example;

import com.github.javaparser.ast.NodeList;
import com.github.javaparser.ast.ImportDeclaration;

public class Simple {

public void lengthImportStatement(NodeList<ImportDeclaration> listOfImports) {
for (ImportDeclaration importDecl : listOfImports) {
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
package com.github.javaparser.ast;

public class ImportDeclaration {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
package com.github.javaparser.ast;

public class NodeList<T> {
}
13 changes: 13 additions & 0 deletions src/test/resources/unsolvedparameter/input/com/example/Simple.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package com.example;

import com.github.javaparser.ast.NodeList;
import com.github.javaparser.ast.ImportDeclaration;

public class Simple {

public void lengthImportStatement(NodeList<ImportDeclaration> listOfImports) {
for (ImportDeclaration importDecl: listOfImports) {
LoiNguyenCS marked this conversation as resolved.
Show resolved Hide resolved

}
}
}