From e8b7eab87ad2151fd559434dc3ec8d005227ac19 Mon Sep 17 00:00:00 2001 From: Loi Nguyen <113363230+LoiNguyenCS@users.noreply.github.com> Date: Fri, 14 Jul 2023 14:01:21 -0400 Subject: [PATCH 01/31] not delete synthetic return types class --- .../java/org/checkerframework/specimin/SpeciminRunner.java | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/main/java/org/checkerframework/specimin/SpeciminRunner.java b/src/main/java/org/checkerframework/specimin/SpeciminRunner.java index bbec2a95..b8d0f17e 100644 --- a/src/main/java/org/checkerframework/specimin/SpeciminRunner.java +++ b/src/main/java/org/checkerframework/specimin/SpeciminRunner.java @@ -156,7 +156,12 @@ public static void main(String... args) throws IOException { for (Entry target : parsedTargetFiles.entrySet()) { // If a compilation output's entire body has been removed, do not output it. if (isEmptyCompilationUnit(target.getValue())) { - continue; + // These classes are synthetic return types of unsolved methods. Even if their bodies are + // empty, they are needed in order for the codes to compile + if (!target.getKey().contains("ReturnType") + && !target.getKey().contains(addMissingClass.getParentClass())) { + continue; + } } Path targetOutputPath = Path.of(outputDirectory, target.getKey()); From 6286bf8510659cff983ab41cb9eb55cfe6e6f867 Mon Sep 17 00:00:00 2001 From: Loi Nguyen <113363230+LoiNguyenCS@users.noreply.github.com> Date: Fri, 14 Jul 2023 14:10:42 -0400 Subject: [PATCH 02/31] add codes for super call --- .../specimin/UnsolvedSymbolVisitor.java | 407 ++++++++++++++---- 1 file changed, 328 insertions(+), 79 deletions(-) diff --git a/src/main/java/org/checkerframework/specimin/UnsolvedSymbolVisitor.java b/src/main/java/org/checkerframework/specimin/UnsolvedSymbolVisitor.java index deefecc6..ed812eae 100644 --- a/src/main/java/org/checkerframework/specimin/UnsolvedSymbolVisitor.java +++ b/src/main/java/org/checkerframework/specimin/UnsolvedSymbolVisitor.java @@ -2,12 +2,21 @@ import com.github.javaparser.ast.ImportDeclaration; import com.github.javaparser.ast.NodeList; +import com.github.javaparser.ast.PackageDeclaration; +import com.github.javaparser.ast.body.ClassOrInterfaceDeclaration; +import com.github.javaparser.ast.body.FieldDeclaration; +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; import com.github.javaparser.ast.expr.MethodCallExpr; import com.github.javaparser.ast.expr.ObjectCreationExpr; import com.github.javaparser.ast.expr.SimpleName; +import com.github.javaparser.ast.stmt.ExplicitConstructorInvocationStmt; import com.github.javaparser.ast.type.PrimitiveType; +import com.github.javaparser.ast.type.ReferenceType; +import com.github.javaparser.ast.type.Type; import com.github.javaparser.ast.visitor.ModifierVisitor; import com.github.javaparser.ast.visitor.Visitable; import com.github.javaparser.resolution.UnsolvedSymbolException; @@ -28,6 +37,7 @@ import java.util.Optional; import java.util.Set; import org.checkerframework.checker.signature.qual.ClassGetSimpleName; +import org.checkerframework.checker.signature.qual.DotSeparatedIdentifiers; import org.checkerframework.checker.signature.qual.FullyQualifiedName; /** @@ -38,6 +48,26 @@ */ public class UnsolvedSymbolVisitor extends ModifierVisitor { + /** + * The parent class of this current class file. If there is no parent class, then the value of + * this variable is an empty string + */ + private @ClassGetSimpleName String parentClass = ""; + + /** The package of this class */ + private String currentPackage = ""; + + /** + * This map will map the name of variables in the current class and its corresponding declaration + */ + private Map variablesAndDeclaration; + + /** + * Based on the method declarations in the current class, this map will map the name of the + * methods with their corresponding return types + */ + private Map methodAndReturnType; + /** List of classes not in the source codes */ private Set missingClass; @@ -45,7 +75,7 @@ public class UnsolvedSymbolVisitor extends ModifierVisitor { private String rootDirectory; /** This instance maps the name of a synthetic method with its synthetic class */ - Map syntheticMethodAndClass; + private Map syntheticMethodAndClass; /** * This is to check if the current synthetic files are enough to prevent UnsolvedSymbolException @@ -84,6 +114,8 @@ public UnsolvedSymbolVisitor(String rootDirectory) { this.classAndPackageMap = new HashMap<>(); this.createdClass = new HashSet<>(); this.syntheticMethodAndClass = new HashMap<>(); + this.methodAndReturnType = new HashMap<>(); + this.variablesAndDeclaration = new HashMap<>(); } /** @@ -126,6 +158,15 @@ private void setclassAndPackageMap() { } } + /** + * Get the value of parentclass + * + * @return parentClass the value of parentClass + */ + public String getParentClass() { + return parentClass; + } + /** * Get the value of gotException * @@ -152,33 +193,99 @@ public void setExceptionToFalse() { gotException = false; } + @Override + public Visitable visit(PackageDeclaration node, Void arg) { + this.currentPackage = node.getNameAsString(); + return super.visit(node, arg); + } + + @Override + public Visitable visit(ClassOrInterfaceDeclaration node, Void arg) { + if (node.getExtendedTypes().isNonEmpty()) { + // note that since Specimin does not have access to the class paths of the project, all the + // unsolved methods related to inheritance will be placed in the parent class, even if there + // is a grandparent class and so forth. + SimpleName parentClassSimpleName = node.getExtendedTypes().get(0).getName(); + parentClass = parentClassSimpleName.asString(); + } + return super.visit(node, arg); + } + + @Override + public Visitable visit(ExplicitConstructorInvocationStmt node, Void arg) { + NodeList arguments = node.getArguments(); + List parametersList = new ArrayList<>(); + for (Expression parameter : arguments) { + if (!canBeSolved(parameter)) { + return super.visit(node, arg); + } + ResolvedType type = parameter.calculateResolvedType(); + if (type instanceof PrimitiveType) { + parametersList.add(type.asPrimitive().name()); + } else if (type instanceof ReferenceType) { + parametersList.add(type.asReferenceType().getQualifiedName()); + } + } + UnsolvedMethod constructorMethod = new UnsolvedMethod(this.parentClass, "", parametersList); + // if the parent class can not be found in the import statements, Specimin assumes it is in the + // same package as the child class, which is also the current class + UnsolvedClass parentClass = + new UnsolvedClass( + this.parentClass, classAndPackageMap.getOrDefault(this.parentClass, currentPackage)); + parentClass.addMethod(constructorMethod); + updateMissingClass(parentClass); + return super.visit(node, arg); + } + + @Override + public Visitable visit(FieldDeclaration node, Void arg) { + for (VariableDeclarator var : node.getVariables()) { + String variableName = var.getNameAsString(); + String variableType = node.getElementType().asString(); + Optional potentialValue = var.getInitializer(); + String variableDeclaration = variableType + " " + variableName; + if (!potentialValue.isEmpty()) { + String variableValue = potentialValue.get().toString(); + variableDeclaration += " = " + variableValue; + } + variablesAndDeclaration.put(variableName, variableDeclaration); + } + return super.visit(node, arg); + } + + @Override + public Visitable visit(MethodDeclaration node, Void arg) { + Type nodeType = node.getType(); + // since this is a return type of a method, it is a dot-separated identifier + @SuppressWarnings("signature") + @DotSeparatedIdentifiers String nodeTypeAsString = nodeType.asString(); + @ClassGetSimpleName String nodeTypeSimpleForm = toSimpleName(nodeTypeAsString); + methodAndReturnType.put(node.getNameAsString(), nodeTypeSimpleForm); + return super.visit(node, arg); + } + + public Visitable visit(FieldAccessExpr node, Void p) { + if (isASuperCall(node) && !canBeSolved(node)) { + updateSyntheticClassForSuperCall(node); + } + return super.visit(node, p); + } + @Override public Visitable visit(MethodCallExpr method, Void p) { - String methodSimpleName = method.getNameAsString(); + if (isASuperCall(method) && !canBeSolved(method)) { + updateSyntheticClassForSuperCall(method); + return super.visit(method, p); + } + // we will wait for the next run to solve this method call if (!canSolveParameters(method)) { return super.visit(method, p); } if (unsolvedAndNotSimple(method)) { updateClassSetWithNotSimpleMethodCall(method); } else if (calledByAnIncompleteSyntheticClass(method)) { - String incompleteClassName = getSyntheticClass(method); - UnsolvedClass missingClass = - new UnsolvedClass( - incompleteClassName, - classAndPackageMap.getOrDefault(incompleteClassName, this.chosenPackage)); - UnsolvedClass returnTypeForThisMethod = - new UnsolvedClass(returnNameForMethod(methodSimpleName), missingClass.getPackageName()); - UnsolvedMethod thisMethod = - new UnsolvedMethod( - methodSimpleName, - returnTypeForThisMethod.getClassName(), - getArgumentsFromMethodCall(method)); - missingClass.addMethod(thisMethod); - classAndPackageMap.put( - returnTypeForThisMethod.getClassName(), returnTypeForThisMethod.getPackageName()); - this.updateMissingClass(missingClass); - this.updateMissingClass(returnTypeForThisMethod); - syntheticMethodAndClass.put(methodSimpleName, missingClass); + @ClassGetSimpleName String incompleteClassName = getSyntheticClass(method); + updateUnsolvedClassWithMethodCall(method, incompleteClassName, ""); } this.gotException = calledByAnUnsolvedSymbol(method) @@ -187,6 +294,187 @@ public Visitable visit(MethodCallExpr method, Void p) { return super.visit(method, p); } + @Override + public Visitable visit(Parameter parameter, Void p) { + try { + parameter.resolve().describeType(); + return super.visit(parameter, p); + } catch (UnsolvedSymbolException e) { + String parameterInString = parameter.toString(); + if (isAClassPath(parameterInString)) { + // parameterInString needs to be a fully-qualified name. As this parameter has a form of + // class path, we can say that it is a fully-qualified name + @SuppressWarnings("signature") + UnsolvedClass newClass = getSimpleSyntheticClassFromFullyQualifiedName(parameterInString); + updateMissingClass(newClass); + } 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); + } + } + gotException = true; + return super.visit(parameter, p); + } + + @Override + public Visitable visit(ObjectCreationExpr newExpr, Void p) { + SimpleName typeName = newExpr.getType().getName(); + String type = typeName.asString(); + Expression asExpression = newExpr; + if (canBeSolved(asExpression)) { + return super.visit(newExpr, p); + } + this.gotException = true; + 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); + } catch (Exception q) { + // can not solve the parameters for this object creation in this current run + return super.visit(newExpr, p); + } + return super.visit(newExpr, p); + } + + /** + * Given a class name that can either be fully-qualified or simple, this method will convert that + * class name to a simple name. + * + * @param className the class name to be converted + * @return the simple form of that class name + */ + // We can have certainty that this method is true as the last element of a class name is the + // simple form of that name + @SuppressWarnings("signature") + public static @ClassGetSimpleName String toSimpleName(@DotSeparatedIdentifiers String className) { + String[] elements = className.split("."); + if (elements.length < 2) { + return className; + } + return elements[elements.length - 1]; + } + + /** + * This method will add a new method declaration to a synthetic class based on the unsolved method + * call in the original input. User can choose the desired return type for the added method. The + * desired return type can be an empty string, and in that case, Specimin will create another + * synthetic class to be the return type of that method. + * + * @param method the method call in the original input + * @param className the name of the synthetic class + * @param desiredReturnType the desired return type for this method + */ + public void updateUnsolvedClassWithMethodCall( + MethodCallExpr method, + @ClassGetSimpleName String className, + @ClassGetSimpleName String desiredReturnType) { + String methodName = method.getNameAsString(); + String returnType = ""; + if (desiredReturnType.equals("")) { + returnType = returnNameForMethod(methodName); + } else { + returnType = desiredReturnType; + } + UnsolvedClass missingClass = + new UnsolvedClass( + className, classAndPackageMap.getOrDefault(className, this.chosenPackage)); + UnsolvedMethod thisMethod = + new UnsolvedMethod(methodName, returnType, getArgumentsFromMethodCall(method)); + missingClass.addMethod(thisMethod); + syntheticMethodAndClass.put(methodName, missingClass); + this.updateMissingClass(missingClass); + if (desiredReturnType.equals("")) { + UnsolvedClass returnTypeForThisMethod = + new UnsolvedClass(returnType, missingClass.getPackageName()); + this.updateMissingClass(returnTypeForThisMethod); + classAndPackageMap.put( + returnTypeForThisMethod.getClassName(), returnTypeForThisMethod.getPackageName()); + } + } + + /** + * This method checks if an expression is called by the super keyword. For example, super.visit() + * is such an expression. + * + * @param node the expression to be checked + * @return true if method is a super call + */ + public static boolean isASuperCall(Expression node) { + if (node instanceof MethodCallExpr) { + Optional caller = node.asMethodCallExpr().getScope(); + if (caller.isEmpty()) { + return false; + } + return caller.get().isSuperExpr(); + } else if (node instanceof FieldAccessExpr) { + Expression caller = node.asFieldAccessExpr().getScope(); + return caller.isSuperExpr(); + } else { + throw new RuntimeException("Unforeseen expression: " + node); + } + } + + /** + * For a super call, this method will update the corresponding synthetic class + * + * @param expr the super call expression to be taken as input + */ + public void updateSyntheticClassForSuperCall(Expression expr) { + if (!isASuperCall(expr)) { + throw new RuntimeException( + "Check if isASuperCall returns true before calling updateSyntheticClassForSuperCall"); + } + if (expr instanceof MethodCallExpr) { + updateUnsolvedClassWithMethodCall( + expr.asMethodCallExpr(), + this.parentClass, + methodAndReturnType.getOrDefault(expr.asMethodCallExpr().getNameAsString(), "")); + } else { + updateUnsolvedClassWithVariables( + expr.asFieldAccessExpr().getNameAsString(), + parentClass, + classAndPackageMap.getOrDefault(parentClass, this.currentPackage)); + } + } + + /** + * This method will add a new variable declaration to a synthetic class. This class is mainly used + * for unsolved parent class. The declaration of the variable in the parent class will be the same + * as the declaration in the child class since Specimin does not have access to much information. + * If the variable is not found in the child class, Specimin will create a synthetic class to be + * the type of that variable. + * + * @param var the variable to be added + * @param className the name of the synthetic class + * @param packageName the package of the synthetic class + */ + public void updateUnsolvedClassWithVariables( + String var, @ClassGetSimpleName String className, String packageName) { + UnsolvedClass relatedClass = new UnsolvedClass(className, packageName); + if (variablesAndDeclaration.containsKey(var)) { + String variableExpression = variablesAndDeclaration.get(var); + relatedClass.addVariables(variableExpression); + updateMissingClass(relatedClass); + } else { + // since it is just simple string combination, it is a simple name + @SuppressWarnings("signature") + @ClassGetSimpleName String variableType = "SyntheticTypeFor" + toCapital(var); + UnsolvedClass varType = new UnsolvedClass(variableType, packageName); + String variableExpression = + String.format("%s %s = new %s();", variableType, var, variableType); + relatedClass.addVariables(variableExpression); + updateMissingClass(relatedClass); + updateMissingClass(varType); + } + } + /** * This method checks if the current run of UnsolvedSymbolVisitor can solve the parameters' types * of a method call @@ -200,9 +488,7 @@ public static boolean canSolveParameters(MethodCallExpr method) { return true; } for (Expression parameter : paraList) { - try { - String type = parameter.calculateResolvedType().describe(); - } catch (Exception e) { + if (!canBeSolved(parameter)) { return false; } } @@ -220,7 +506,6 @@ public static List getArgumentsFromMethodCall(MethodCallExpr method) { NodeList paraList = method.getArguments(); for (Expression parameter : paraList) { ResolvedType type = parameter.calculateResolvedType(); - // for reference type, we need the fully-qualified name to avoid having to add additional // import statements. if (type.isReferenceType()) { @@ -266,11 +551,23 @@ public static boolean calledByAnUnsolvedSymbol(MethodCallExpr method) { return false; } Expression callerExpression = caller.get(); + return canBeSolved(callerExpression); + } + + /** + * This methods check if an Expression instance can be solved by SymbolSolver of JavaParser. If + * the Expression instance can be solved, there is no need to create any synthetic method or class + * for it. + * + * @param expr the expression to be checked + * @return true if the expression can be solved + */ + public static boolean canBeSolved(Expression expr) { try { - callerExpression.calculateResolvedType(); - return false; - } catch (Exception e) { + expr.calculateResolvedType().describe(); return true; + } catch (Exception e) { + return false; } } @@ -348,57 +645,6 @@ public static boolean calledByAnIncompleteSyntheticClass(MethodCallExpr method) return returnName; } - @Override - public Visitable visit(Parameter parameter, Void p) { - try { - parameter.resolve().describeType(); - return super.visit(parameter, p); - } catch (UnsolvedSymbolException e) { - String parameterInString = parameter.toString(); - if (isAClassPath(parameterInString)) { - // parameterInString needs to be a fully-qualified name. As this parameter has a form of - // class path, we can say that it is a fully-qualified name - @SuppressWarnings("signature") - UnsolvedClass newClass = getSimpleSyntheticClassFromFullyQualifiedName(parameterInString); - updateMissingClass(newClass); - } 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); - } - } - // there are more elegant ways to update gotException, but the compiler will throw an error if - // the try block doesn't have any use - gotException = true; - return super.visit(parameter, p); - } - - @Override - public Visitable visit(ObjectCreationExpr newExpr, Void p) { - SimpleName typeName = newExpr.getType().getName(); - String type = typeName.asString(); - try { - type = newExpr.resolve().getQualifiedName(); - } catch (UnsolvedSymbolException e) { - 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); - } catch (Exception q) { - this.gotException = true; - return super.visit(newExpr, p); - } - } - this.gotException = type.equals(newExpr.getTypeAsString()); - return super.visit(newExpr, p); - } - /** * This method is to update the missingClass list. The reason we have this update is to add a * method to an existing class. @@ -415,6 +661,9 @@ public void updateMissingClass(UnsolvedClass missedClass) { e.addMethod(method); } } + for (String variablesDescription : missedClass.getClassVariables()) { + e.addVariables(variablesDescription); + } return; } } @@ -564,7 +813,7 @@ 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 methodCall the method call to be taken as input + * @param method the method call to be taken as input */ public void updateClassSetWithNotSimpleMethodCall(MethodCallExpr method) { String methodCall = method.toString(); From cefeeb6f423ccf2687ee29ff970372bf3f73302b Mon Sep 17 00:00:00 2001 From: Loi Nguyen <113363230+LoiNguyenCS@users.noreply.github.com> Date: Fri, 14 Jul 2023 14:14:34 -0400 Subject: [PATCH 03/31] include constructor in TargetMethodFinder --- .../specimin/TargetMethodFinderVisitor.java | 28 +++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/src/main/java/org/checkerframework/specimin/TargetMethodFinderVisitor.java b/src/main/java/org/checkerframework/specimin/TargetMethodFinderVisitor.java index 60bd574e..0455ae3e 100644 --- a/src/main/java/org/checkerframework/specimin/TargetMethodFinderVisitor.java +++ b/src/main/java/org/checkerframework/specimin/TargetMethodFinderVisitor.java @@ -1,9 +1,11 @@ package org.checkerframework.specimin; 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.expr.MethodCallExpr; import com.github.javaparser.ast.expr.ObjectCreationExpr; +import com.github.javaparser.ast.stmt.ExplicitConstructorInvocationStmt; import com.github.javaparser.ast.visitor.ModifierVisitor; import com.github.javaparser.ast.visitor.Visitable; import java.util.ArrayList; @@ -134,6 +136,23 @@ public Visitable visit(ClassOrInterfaceDeclaration decl, Void p) { return result; } + @Override + public Visitable visit(ConstructorDeclaration method, Void p) { + String constructorMethodAsString = method.getDeclarationAsString(false, false, false); + String methodName = + this.classFQName + + "#" + + constructorMethodAsString.substring(constructorMethodAsString.indexOf(' ') + 1); + if (this.targetMethodNames.contains(methodName)) { + insideTargetMethod = true; + targetMethods.add(method.resolve().getQualifiedSignature()); + unfoundMethods.remove(methodName); + } + Visitable result = super.visit(method, p); + insideTargetMethod = false; + return result; + } + @Override public Visitable visit(MethodDeclaration method, Void p) { String methodDeclAsString = method.getDeclarationAsString(false, false, false); @@ -168,4 +187,13 @@ public Visitable visit(ObjectCreationExpr newExpr, Void p) { } return super.visit(newExpr, p); } + + @Override + public Visitable visit(ExplicitConstructorInvocationStmt expr, Void p) { + if (insideTargetMethod) { + usedMethods.add(expr.resolve().getQualifiedSignature()); + usedClass.add(expr.resolve().getPackageName() + "." + expr.resolve().getClassName()); + } + return super.visit(expr, p); + } } From 1a113ed3ea3a85a55529759635c6577d512f4a8c Mon Sep 17 00:00:00 2001 From: Loi Nguyen <113363230+LoiNguyenCS@users.noreply.github.com> Date: Fri, 14 Jul 2023 14:15:07 -0400 Subject: [PATCH 04/31] add fields to class --- .../specimin/UnsolvedClass.java | 27 +++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/src/main/java/org/checkerframework/specimin/UnsolvedClass.java b/src/main/java/org/checkerframework/specimin/UnsolvedClass.java index fa5575f3..6ac375a1 100644 --- a/src/main/java/org/checkerframework/specimin/UnsolvedClass.java +++ b/src/main/java/org/checkerframework/specimin/UnsolvedClass.java @@ -15,6 +15,9 @@ public class UnsolvedClass { /** The name of the class */ private final @ClassGetSimpleName String className; + /** The variables of this class */ + private final Set classVariables; + /** * The name of the package of the class. We rely on the import statements from the source codes to * guess the package name. @@ -31,6 +34,7 @@ public UnsolvedClass(@ClassGetSimpleName String className, String packageName) { this.className = className; this.methods = new HashSet<>(); this.packageName = packageName; + this.classVariables = new HashSet<>(); } /** @@ -59,6 +63,16 @@ public Set getMethods() { public String getPackageName() { return packageName; } + + /** + * Get the variables of this current class + * + * @return classVariables + */ + public Set getClassVariables() { + return classVariables; + } + /** * Add a method to the class * @@ -68,6 +82,16 @@ public void addMethod(UnsolvedMethod method) { this.methods.add(method); } + /** + * Add variables expression to the class. We expect something like "int i" or "String y" instead + * of just "i" and "y" + * + * @param variableExpression the expression of the variables to be added + */ + public void addVariables(String variableExpression) { + this.classVariables.add(variableExpression); + } + /** * 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. @@ -94,6 +118,9 @@ public String toString() { StringBuilder sb = new StringBuilder(); sb.append("package ").append(packageName).append(";\n"); sb.append("public class ").append(className).append(" {\n"); + for (String variableDeclarations : classVariables) { + sb.append(" " + variableDeclarations + ";\n"); + } for (UnsolvedMethod method : methods) { sb.append(method.toString()); } From 554422800c5b611fdff77a623846ea9315cb354b Mon Sep 17 00:00:00 2001 From: unknown Date: Fri, 14 Jul 2023 20:07:45 -0400 Subject: [PATCH 05/31] Add test files --- .../specimin/SyntheticSuperConstructor.java | 18 ++++++++++++++++++ .../specimin/SyntheticSuperMethod.java | 18 ++++++++++++++++++ .../specimin/SyntheticSuperVariables.java | 18 ++++++++++++++++++ .../expected/com/example/Car.java | 8 ++++++++ .../expected/org/factory/Vehicle.java | 7 +++++++ .../input/com/example/Car.java | 8 ++++++++ .../expected/com/example/Car.java | 8 ++++++++ .../expected/org/factory/Vehicle.java | 7 +++++++ .../input/com/example/Car.java | 8 ++++++++ .../expected/com/example/Dog.java | 14 ++++++++++++++ .../expected/org/wild/Mammal.java | 5 +++++ .../input/com/example/Dog.java | 14 ++++++++++++++ 12 files changed, 133 insertions(+) create mode 100644 src/test/java/org/checkerframework/specimin/SyntheticSuperConstructor.java create mode 100644 src/test/java/org/checkerframework/specimin/SyntheticSuperMethod.java create mode 100644 src/test/java/org/checkerframework/specimin/SyntheticSuperVariables.java create mode 100644 src/test/resources/syntheticsuperconstructor/expected/com/example/Car.java create mode 100644 src/test/resources/syntheticsuperconstructor/expected/org/factory/Vehicle.java create mode 100644 src/test/resources/syntheticsuperconstructor/input/com/example/Car.java create mode 100644 src/test/resources/syntheticsupermethod/expected/com/example/Car.java create mode 100644 src/test/resources/syntheticsupermethod/expected/org/factory/Vehicle.java create mode 100644 src/test/resources/syntheticsupermethod/input/com/example/Car.java create mode 100644 src/test/resources/syntheticsupervariables/expected/com/example/Dog.java create mode 100644 src/test/resources/syntheticsupervariables/expected/org/wild/Mammal.java create mode 100644 src/test/resources/syntheticsupervariables/input/com/example/Dog.java diff --git a/src/test/java/org/checkerframework/specimin/SyntheticSuperConstructor.java b/src/test/java/org/checkerframework/specimin/SyntheticSuperConstructor.java new file mode 100644 index 00000000..58d085aa --- /dev/null +++ b/src/test/java/org/checkerframework/specimin/SyntheticSuperConstructor.java @@ -0,0 +1,18 @@ +package org.checkerframework.specimin; + +import java.io.IOException; +import org.junit.Test; + +/** + * This test checks that if Specimin will work properly where there are two classes with the same + * name + */ +public class SameClassName { + @Test + public void runTest() throws IOException { + SpeciminTestExecutor.runTest( + "sameclassname", + new String[] {"com/example/Simple.java"}, + new String[] {"com.example.Simple#secondCalculator()"}); + } +} diff --git a/src/test/java/org/checkerframework/specimin/SyntheticSuperMethod.java b/src/test/java/org/checkerframework/specimin/SyntheticSuperMethod.java new file mode 100644 index 00000000..c2f2ea3b --- /dev/null +++ b/src/test/java/org/checkerframework/specimin/SyntheticSuperMethod.java @@ -0,0 +1,18 @@ +package org.checkerframework.specimin; + +import org.junit.Test; + +import java.io.IOException; + +/** + * This test checks that if Specimin will work properly where there is a super constructor while the parent class file is not in the root directory physically + */ +public class SyntheticSuperMethod { + @Test + public void runTest() throws IOException { + SpeciminTestExecutor.runTest( + "syntheticsuperconstructor", + new String[] {"com/example/Car.java"}, + new String[] {"com.example.Car#Car()"}); + } +} diff --git a/src/test/java/org/checkerframework/specimin/SyntheticSuperVariables.java b/src/test/java/org/checkerframework/specimin/SyntheticSuperVariables.java new file mode 100644 index 00000000..a45f5e01 --- /dev/null +++ b/src/test/java/org/checkerframework/specimin/SyntheticSuperVariables.java @@ -0,0 +1,18 @@ +package org.checkerframework.specimin; + +import org.junit.Test; + +import java.io.IOException; + +/** + * This test checks that if Specimin will work properly where there is a super method call while the parent class file is not in the root directory physically + */ +public class SyntheticSuperMethod { + @Test + public void runTest() throws IOException { + SpeciminTestExecutor.runTest( + "syntheticsupermethod", + new String[] {"com/example/Car.java"}, + new String[] {"com.example.Car#getWheels()"}); + } +} diff --git a/src/test/resources/syntheticsuperconstructor/expected/com/example/Car.java b/src/test/resources/syntheticsuperconstructor/expected/com/example/Car.java new file mode 100644 index 00000000..9b03bc11 --- /dev/null +++ b/src/test/resources/syntheticsuperconstructor/expected/com/example/Car.java @@ -0,0 +1,8 @@ +package com.example; +import org.factory.Vehicle; + +public class Car extends Vehicle { + public Car() { + super(); + } +} diff --git a/src/test/resources/syntheticsuperconstructor/expected/org/factory/Vehicle.java b/src/test/resources/syntheticsuperconstructor/expected/org/factory/Vehicle.java new file mode 100644 index 00000000..313ba6fe --- /dev/null +++ b/src/test/resources/syntheticsuperconstructor/expected/org/factory/Vehicle.java @@ -0,0 +1,7 @@ +package org.factory; +public class Vehicle { + + public Vehicle() { + throw new Error(); + } +} diff --git a/src/test/resources/syntheticsuperconstructor/input/com/example/Car.java b/src/test/resources/syntheticsuperconstructor/input/com/example/Car.java new file mode 100644 index 00000000..9b03bc11 --- /dev/null +++ b/src/test/resources/syntheticsuperconstructor/input/com/example/Car.java @@ -0,0 +1,8 @@ +package com.example; +import org.factory.Vehicle; + +public class Car extends Vehicle { + public Car() { + super(); + } +} diff --git a/src/test/resources/syntheticsupermethod/expected/com/example/Car.java b/src/test/resources/syntheticsupermethod/expected/com/example/Car.java new file mode 100644 index 00000000..b177f669 --- /dev/null +++ b/src/test/resources/syntheticsupermethod/expected/com/example/Car.java @@ -0,0 +1,8 @@ +package com.example; +import org.factory.Vehicle; + +public class Car extends Vehicle { + public int getWheels() { + return super.getWheels(); + } +} diff --git a/src/test/resources/syntheticsupermethod/expected/org/factory/Vehicle.java b/src/test/resources/syntheticsupermethod/expected/org/factory/Vehicle.java new file mode 100644 index 00000000..a4e863ea --- /dev/null +++ b/src/test/resources/syntheticsupermethod/expected/org/factory/Vehicle.java @@ -0,0 +1,7 @@ +package org.factory; +public class Vehicle { + + public int getWheels() { + throw new Error(); + } +} diff --git a/src/test/resources/syntheticsupermethod/input/com/example/Car.java b/src/test/resources/syntheticsupermethod/input/com/example/Car.java new file mode 100644 index 00000000..b177f669 --- /dev/null +++ b/src/test/resources/syntheticsupermethod/input/com/example/Car.java @@ -0,0 +1,8 @@ +package com.example; +import org.factory.Vehicle; + +public class Car extends Vehicle { + public int getWheels() { + return super.getWheels(); + } +} diff --git a/src/test/resources/syntheticsupervariables/expected/com/example/Dog.java b/src/test/resources/syntheticsupervariables/expected/com/example/Dog.java new file mode 100644 index 00000000..fc77ebdd --- /dev/null +++ b/src/test/resources/syntheticsupervariables/expected/com/example/Dog.java @@ -0,0 +1,14 @@ +package com.example; +import org.wild.Mammal; + +public class Dog extends Mammal { + String habitat; + boolean canBreathUnderWater; + public void Dog() { + habitat = super.habitat; + canBreathUnderWater = super.canBreathUnderWater; + + + } + +} diff --git a/src/test/resources/syntheticsupervariables/expected/org/wild/Mammal.java b/src/test/resources/syntheticsupervariables/expected/org/wild/Mammal.java new file mode 100644 index 00000000..73c02f0f --- /dev/null +++ b/src/test/resources/syntheticsupervariables/expected/org/wild/Mammal.java @@ -0,0 +1,5 @@ +package org.wild; +public class Mammal { + boolean canBreathUnderWater = false; + String habitat = null; +} diff --git a/src/test/resources/syntheticsupervariables/input/com/example/Dog.java b/src/test/resources/syntheticsupervariables/input/com/example/Dog.java new file mode 100644 index 00000000..fc77ebdd --- /dev/null +++ b/src/test/resources/syntheticsupervariables/input/com/example/Dog.java @@ -0,0 +1,14 @@ +package com.example; +import org.wild.Mammal; + +public class Dog extends Mammal { + String habitat; + boolean canBreathUnderWater; + public void Dog() { + habitat = super.habitat; + canBreathUnderWater = super.canBreathUnderWater; + + + } + +} From 5d938e116f1b3461f4570a324cdc81ee6d987942 Mon Sep 17 00:00:00 2001 From: unknown Date: Fri, 14 Jul 2023 20:10:22 -0400 Subject: [PATCH 06/31] Add codes for extension classes --- .../specimin/SpeciminRunner.java | 4 +- .../specimin/TargetMethodFinderVisitor.java | 23 +++++++--- .../specimin/UnsolvedSymbolVisitor.java | 42 ++++++++++++++++++- 3 files changed, 60 insertions(+), 9 deletions(-) diff --git a/src/main/java/org/checkerframework/specimin/SpeciminRunner.java b/src/main/java/org/checkerframework/specimin/SpeciminRunner.java index b8d0f17e..83f83206 100644 --- a/src/main/java/org/checkerframework/specimin/SpeciminRunner.java +++ b/src/main/java/org/checkerframework/specimin/SpeciminRunner.java @@ -77,6 +77,7 @@ public static void main(String... args) throws IOException { * files in the end. */ Set createdClass = new HashSet<>(); + System.out.println("Get in the loop"); while (addMissingClass.gettingException()) { addMissingClass.setExceptionToFalse(); for (CompilationUnit cu : parsedTargetFiles.values()) { @@ -97,7 +98,7 @@ public static void main(String... args) throws IOException { } } List targetMethodNames = options.valuesOf(targetMethodsOption); - + System.out.println("Get out the loop"); // Use a two-phase approach: the first phase finds the target(s) and records // what specifications they use, and the second phase takes that information // and removes all non-used code. @@ -127,7 +128,6 @@ public static void main(String... args) throws IOException { relatedClass.add(directoryOfFile); } } - // correct the types of all related files before adding them to parsedTargetFiles JavaTypeCorrect typeCorrecter = new JavaTypeCorrect(root, relatedClass); typeCorrecter.correctTypesForAllFiles(); diff --git a/src/main/java/org/checkerframework/specimin/TargetMethodFinderVisitor.java b/src/main/java/org/checkerframework/specimin/TargetMethodFinderVisitor.java index 0455ae3e..6fd13978 100644 --- a/src/main/java/org/checkerframework/specimin/TargetMethodFinderVisitor.java +++ b/src/main/java/org/checkerframework/specimin/TargetMethodFinderVisitor.java @@ -3,15 +3,11 @@ 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.expr.MethodCallExpr; -import com.github.javaparser.ast.expr.ObjectCreationExpr; +import com.github.javaparser.ast.expr.*; import com.github.javaparser.ast.stmt.ExplicitConstructorInvocationStmt; import com.github.javaparser.ast.visitor.ModifierVisitor; import com.github.javaparser.ast.visitor.Visitable; -import java.util.ArrayList; -import java.util.HashSet; -import java.util.List; -import java.util.Set; +import java.util.*; /** * The main visitor for Specimin's first phase, which locates the target method(s) and compiles @@ -59,6 +55,12 @@ public class TargetMethodFinderVisitor extends ModifierVisitor { */ private final List unfoundMethods; + /** The package of this current class */ + private String packageName = ""; + + /** The parent class of this class, if any. */ + private String parentClass = ""; + /** * Create a new target method finding visitor. * @@ -196,4 +198,13 @@ public Visitable visit(ExplicitConstructorInvocationStmt expr, Void p) { } return super.visit(expr, p); } + + @Override + public Visitable visit(FieldAccessExpr expr, Void p) { + Expression caller = expr.getScope(); + if (caller instanceof SuperExpr) { + usedClass.add(caller.calculateResolvedType().describe()); + } + return super.visit(expr, p); + } } diff --git a/src/main/java/org/checkerframework/specimin/UnsolvedSymbolVisitor.java b/src/main/java/org/checkerframework/specimin/UnsolvedSymbolVisitor.java index ed812eae..030e3abc 100644 --- a/src/main/java/org/checkerframework/specimin/UnsolvedSymbolVisitor.java +++ b/src/main/java/org/checkerframework/specimin/UnsolvedSymbolVisitor.java @@ -247,6 +247,9 @@ public Visitable visit(FieldDeclaration node, Void arg) { if (!potentialValue.isEmpty()) { String variableValue = potentialValue.get().toString(); variableDeclaration += " = " + variableValue; + } else { + variableDeclaration = + this.setInitialValueForVariableDeclaration(variableType, variableDeclaration); } variablesAndDeclaration.put(variableName, variableDeclaration); } @@ -287,6 +290,9 @@ public Visitable visit(MethodCallExpr method, Void p) { @ClassGetSimpleName String incompleteClassName = getSyntheticClass(method); updateUnsolvedClassWithMethodCall(method, incompleteClassName, ""); } + System.out.println(calledByAnUnsolvedSymbol(method)); + System.out.println(calledByAnIncompleteSyntheticClass(method)); + System.out.println(unsolvedAndNotSimple(method)); this.gotException = calledByAnUnsolvedSymbol(method) || calledByAnIncompleteSyntheticClass(method) @@ -343,6 +349,39 @@ public Visitable visit(ObjectCreationExpr newExpr, Void p) { return super.visit(newExpr, p); } + /** + * Given the variable type and the basic declaration of that variable (such as "int x", "boolean + * y", "Car redTruck",...), this methods will add an initial value to that declaration of the + * variable. The way the initial value is chosen is based on the document of the Java Language: + * https://docs.oracle.com/javase/specs/jls/se7/html/jls-4.html#jls-4.12.5 + * + * @param variableType the type of the variable + * @param variableDeclaration the basic declaration of that variable + * @return the declaration of the variable with an initial value + */ + public static String setInitialValueForVariableDeclaration( + String variableType, String variableDeclaration) { + if (variableType.equals("byte")) { + return variableDeclaration + " = (byte)0"; + } else if (variableType.equals("short")) { + return variableDeclaration + " = (short)0"; + } else if (variableType.equals("int")) { + return variableDeclaration + " = 0"; + } else if (variableType.equals("long")) { + return variableDeclaration + " = 0L"; + } else if (variableType.equals("float")) { + return variableDeclaration + " = 0.0f"; + } else if (variableType.equals("double")) { + return variableDeclaration + " = 0.0d"; + } else if (variableType.equals("char")) { + return variableDeclaration + " = '\\u0000'"; + } else if (variableType.equals("boolean")) { + return variableDeclaration + " = false"; + } else { + return variableDeclaration + " = null"; + } + } + /** * Given a class name that can either be fully-qualified or simple, this method will convert that * class name to a simple name. @@ -551,7 +590,7 @@ public static boolean calledByAnUnsolvedSymbol(MethodCallExpr method) { return false; } Expression callerExpression = caller.get(); - return canBeSolved(callerExpression); + return !canBeSolved(callerExpression); } /** @@ -567,6 +606,7 @@ public static boolean canBeSolved(Expression expr) { expr.calculateResolvedType().describe(); return true; } catch (Exception e) { + System.out.println(e); return false; } } From 6a85d3758d74bc99d1a1354ceec7b91a273b350e Mon Sep 17 00:00:00 2001 From: unknown Date: Fri, 14 Jul 2023 20:19:37 -0400 Subject: [PATCH 07/31] do some cleaning --- .../specimin/SpeciminRunner.java | 2 -- .../specimin/TargetMethodFinderVisitor.java | 17 +++++++++-------- .../specimin/UnsolvedSymbolVisitor.java | 1 - 3 files changed, 9 insertions(+), 11 deletions(-) diff --git a/src/main/java/org/checkerframework/specimin/SpeciminRunner.java b/src/main/java/org/checkerframework/specimin/SpeciminRunner.java index 83f83206..b43e5f7d 100644 --- a/src/main/java/org/checkerframework/specimin/SpeciminRunner.java +++ b/src/main/java/org/checkerframework/specimin/SpeciminRunner.java @@ -77,7 +77,6 @@ public static void main(String... args) throws IOException { * files in the end. */ Set createdClass = new HashSet<>(); - System.out.println("Get in the loop"); while (addMissingClass.gettingException()) { addMissingClass.setExceptionToFalse(); for (CompilationUnit cu : parsedTargetFiles.values()) { @@ -98,7 +97,6 @@ public static void main(String... args) throws IOException { } } List targetMethodNames = options.valuesOf(targetMethodsOption); - System.out.println("Get out the loop"); // Use a two-phase approach: the first phase finds the target(s) and records // what specifications they use, and the second phase takes that information // and removes all non-used code. diff --git a/src/main/java/org/checkerframework/specimin/TargetMethodFinderVisitor.java b/src/main/java/org/checkerframework/specimin/TargetMethodFinderVisitor.java index 6fd13978..b262b692 100644 --- a/src/main/java/org/checkerframework/specimin/TargetMethodFinderVisitor.java +++ b/src/main/java/org/checkerframework/specimin/TargetMethodFinderVisitor.java @@ -3,11 +3,18 @@ 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.expr.*; +import com.github.javaparser.ast.expr.Expression; +import com.github.javaparser.ast.expr.FieldAccessExpr; +import com.github.javaparser.ast.expr.MethodCallExpr; +import com.github.javaparser.ast.expr.ObjectCreationExpr; +import com.github.javaparser.ast.expr.SuperExpr; import com.github.javaparser.ast.stmt.ExplicitConstructorInvocationStmt; import com.github.javaparser.ast.visitor.ModifierVisitor; import com.github.javaparser.ast.visitor.Visitable; -import java.util.*; +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Set; /** * The main visitor for Specimin's first phase, which locates the target method(s) and compiles @@ -55,12 +62,6 @@ public class TargetMethodFinderVisitor extends ModifierVisitor { */ private final List unfoundMethods; - /** The package of this current class */ - private String packageName = ""; - - /** The parent class of this class, if any. */ - private String parentClass = ""; - /** * Create a new target method finding visitor. * diff --git a/src/main/java/org/checkerframework/specimin/UnsolvedSymbolVisitor.java b/src/main/java/org/checkerframework/specimin/UnsolvedSymbolVisitor.java index 030e3abc..2e57a75e 100644 --- a/src/main/java/org/checkerframework/specimin/UnsolvedSymbolVisitor.java +++ b/src/main/java/org/checkerframework/specimin/UnsolvedSymbolVisitor.java @@ -606,7 +606,6 @@ public static boolean canBeSolved(Expression expr) { expr.calculateResolvedType().describe(); return true; } catch (Exception e) { - System.out.println(e); return false; } } From 087c4dc6b2919d2e9cca1d19a7df232c56c0093a Mon Sep 17 00:00:00 2001 From: unknown Date: Fri, 14 Jul 2023 20:24:35 -0400 Subject: [PATCH 08/31] Add missing override annotation --- .../org/checkerframework/specimin/UnsolvedSymbolVisitor.java | 1 + 1 file changed, 1 insertion(+) diff --git a/src/main/java/org/checkerframework/specimin/UnsolvedSymbolVisitor.java b/src/main/java/org/checkerframework/specimin/UnsolvedSymbolVisitor.java index 2e57a75e..26da9245 100644 --- a/src/main/java/org/checkerframework/specimin/UnsolvedSymbolVisitor.java +++ b/src/main/java/org/checkerframework/specimin/UnsolvedSymbolVisitor.java @@ -267,6 +267,7 @@ public Visitable visit(MethodDeclaration node, Void arg) { return super.visit(node, arg); } + @Override public Visitable visit(FieldAccessExpr node, Void p) { if (isASuperCall(node) && !canBeSolved(node)) { updateSyntheticClassForSuperCall(node); From b5d7d5248fc8a776393f2077abbb17e5a388c204 Mon Sep 17 00:00:00 2001 From: unknown Date: Fri, 14 Jul 2023 20:27:23 -0400 Subject: [PATCH 09/31] fix failed build --- .../specimin/SyntheticSuperConstructor.java | 12 ++++++------ .../specimin/SyntheticSuperMethod.java | 10 +++++----- .../specimin/SyntheticSuperVariables.java | 14 +++++++------- 3 files changed, 18 insertions(+), 18 deletions(-) diff --git a/src/test/java/org/checkerframework/specimin/SyntheticSuperConstructor.java b/src/test/java/org/checkerframework/specimin/SyntheticSuperConstructor.java index 58d085aa..30d0ebf2 100644 --- a/src/test/java/org/checkerframework/specimin/SyntheticSuperConstructor.java +++ b/src/test/java/org/checkerframework/specimin/SyntheticSuperConstructor.java @@ -4,15 +4,15 @@ import org.junit.Test; /** - * This test checks that if Specimin will work properly where there are two classes with the same - * name + * This test checks that if Specimin will work properly where there is a super constructor while the + * parent class file is not in the root directory physically */ -public class SameClassName { +public class SyntheticSuperConstructor { @Test public void runTest() throws IOException { SpeciminTestExecutor.runTest( - "sameclassname", - new String[] {"com/example/Simple.java"}, - new String[] {"com.example.Simple#secondCalculator()"}); + "syntheticsuperconstructor", + new String[] {"com/example/Car.java"}, + new String[] {"com.example.Car#Car()"}); } } diff --git a/src/test/java/org/checkerframework/specimin/SyntheticSuperMethod.java b/src/test/java/org/checkerframework/specimin/SyntheticSuperMethod.java index c2f2ea3b..9703d3b6 100644 --- a/src/test/java/org/checkerframework/specimin/SyntheticSuperMethod.java +++ b/src/test/java/org/checkerframework/specimin/SyntheticSuperMethod.java @@ -1,18 +1,18 @@ package org.checkerframework.specimin; -import org.junit.Test; - import java.io.IOException; +import org.junit.Test; /** - * This test checks that if Specimin will work properly where there is a super constructor while the parent class file is not in the root directory physically + * This test checks that if Specimin will work properly where there is a super method call while the + * parent class file is not in the root directory physically */ public class SyntheticSuperMethod { @Test public void runTest() throws IOException { SpeciminTestExecutor.runTest( - "syntheticsuperconstructor", + "syntheticsupermethod", new String[] {"com/example/Car.java"}, - new String[] {"com.example.Car#Car()"}); + new String[] {"com.example.Car#getWheels()"}); } } diff --git a/src/test/java/org/checkerframework/specimin/SyntheticSuperVariables.java b/src/test/java/org/checkerframework/specimin/SyntheticSuperVariables.java index a45f5e01..a3de7588 100644 --- a/src/test/java/org/checkerframework/specimin/SyntheticSuperVariables.java +++ b/src/test/java/org/checkerframework/specimin/SyntheticSuperVariables.java @@ -1,18 +1,18 @@ package org.checkerframework.specimin; -import org.junit.Test; - import java.io.IOException; +import org.junit.Test; /** - * This test checks that if Specimin will work properly where there is a super method call while the parent class file is not in the root directory physically + * This test checks that if Specimin will work properly where there is a super variables call while + * the parent class file is not in the root directory physically */ -public class SyntheticSuperMethod { +public class SyntheticSuperVariables { @Test public void runTest() throws IOException { SpeciminTestExecutor.runTest( - "syntheticsupermethod", - new String[] {"com/example/Car.java"}, - new String[] {"com.example.Car#getWheels()"}); + "syntheticsupervariables", + new String[] {"com/example/Dog.java"}, + new String[] {"com.example.Dog#Dog()"}); } } From f3496c78958e9084eca700d6c82a1a44c3a00f35 Mon Sep 17 00:00:00 2001 From: unknown Date: Fri, 14 Jul 2023 20:42:31 -0400 Subject: [PATCH 10/31] fix bugs and some debug lines --- .../org/checkerframework/specimin/SpeciminRunner.java | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/main/java/org/checkerframework/specimin/SpeciminRunner.java b/src/main/java/org/checkerframework/specimin/SpeciminRunner.java index b43e5f7d..2554912a 100644 --- a/src/main/java/org/checkerframework/specimin/SpeciminRunner.java +++ b/src/main/java/org/checkerframework/specimin/SpeciminRunner.java @@ -154,10 +154,11 @@ public static void main(String... args) throws IOException { for (Entry target : parsedTargetFiles.entrySet()) { // If a compilation output's entire body has been removed, do not output it. if (isEmptyCompilationUnit(target.getValue())) { - // These classes are synthetic return types of unsolved methods. Even if their bodies are - // empty, they are needed in order for the codes to compile - if (!target.getKey().contains("ReturnType") - && !target.getKey().contains(addMissingClass.getParentClass())) { + boolean isASyntheticReturnType = target.getKey().contains("ReturnType"); + boolean isASyntheticSuperClass = + !addMissingClass.getParentClass().equals("") + && target.getKey().contains(addMissingClass.getParentClass()); + if (!isASyntheticSuperClass && !isASyntheticReturnType) { continue; } } From e963be59c3bc0b630b1df157d5d6f1603877ec44 Mon Sep 17 00:00:00 2001 From: unknown Date: Fri, 14 Jul 2023 20:44:23 -0400 Subject: [PATCH 11/31] some remaining debug lines --- .../org/checkerframework/specimin/UnsolvedSymbolVisitor.java | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/main/java/org/checkerframework/specimin/UnsolvedSymbolVisitor.java b/src/main/java/org/checkerframework/specimin/UnsolvedSymbolVisitor.java index 26da9245..eae9d567 100644 --- a/src/main/java/org/checkerframework/specimin/UnsolvedSymbolVisitor.java +++ b/src/main/java/org/checkerframework/specimin/UnsolvedSymbolVisitor.java @@ -291,9 +291,6 @@ public Visitable visit(MethodCallExpr method, Void p) { @ClassGetSimpleName String incompleteClassName = getSyntheticClass(method); updateUnsolvedClassWithMethodCall(method, incompleteClassName, ""); } - System.out.println(calledByAnUnsolvedSymbol(method)); - System.out.println(calledByAnIncompleteSyntheticClass(method)); - System.out.println(unsolvedAndNotSimple(method)); this.gotException = calledByAnUnsolvedSymbol(method) || calledByAnIncompleteSyntheticClass(method) From 3e59244c9b838caaacbe7e69a0f0cbdaf05bb37e Mon Sep 17 00:00:00 2001 From: unknown Date: Mon, 24 Jul 2023 15:09:55 -0400 Subject: [PATCH 12/31] add codes and test for jar files support --- JavaParser.astub | 6 + .../specimin/SpeciminRunner.java | 10 +- .../specimin/UnsolvedSymbolVisitor.java | 196 ++++++++++++++---- .../checkerframework/specimin/CallJDK.java | 3 +- .../specimin/CallJavaParser.java | 3 +- .../specimin/CrossClassVariable.java | 3 +- .../specimin/GlobalVariables.java | 3 +- .../specimin/HiddenTypeTest.java | 3 +- .../specimin/JarFileTest.java | 16 ++ .../NoDependenciesReturnsSameTest.java | 3 +- .../specimin/OneFileSimpleTest.java | 3 +- .../specimin/SameClassName.java | 3 +- .../specimin/SpeciminTestExecutor.java | 7 +- .../checkerframework/specimin/SuperClass.java | 3 +- .../specimin/SyntheticSuperConstructor.java | 3 +- .../specimin/SyntheticSuperMethod.java | 3 +- .../specimin/SyntheticSuperVariables.java | 3 +- .../specimin/ThreeLayerFunction.java | 3 +- .../specimin/TwoFileSimpleTest.java | 3 +- .../specimin/UnusedSecondClassTest.java | 3 +- .../jarfile/expected/an/old/library/Book.java | 11 + .../jarfile/expected/com/example/Simple.java | 11 + .../jarfile/input/com/example/Simple.java | 11 + 23 files changed, 261 insertions(+), 52 deletions(-) create mode 100644 src/test/java/org/checkerframework/specimin/JarFileTest.java create mode 100644 src/test/resources/jarfile/expected/an/old/library/Book.java create mode 100644 src/test/resources/jarfile/expected/com/example/Simple.java create mode 100644 src/test/resources/jarfile/input/com/example/Simple.java diff --git a/JavaParser.astub b/JavaParser.astub index 56fc1320..24a4f762 100644 --- a/JavaParser.astub +++ b/JavaParser.astub @@ -16,4 +16,10 @@ package com.github.javaparser.ast.expr; class SimpleName { @ClassGetSimpleName String asString(); +} + +package com.github.javaparser.resolution.declarations; + +interface ResolvedMethodDeclaration { + @DotSeparatedIdentifiers String getClassName(); } \ No newline at end of file diff --git a/src/main/java/org/checkerframework/specimin/SpeciminRunner.java b/src/main/java/org/checkerframework/specimin/SpeciminRunner.java index 2554912a..4dcd5096 100644 --- a/src/main/java/org/checkerframework/specimin/SpeciminRunner.java +++ b/src/main/java/org/checkerframework/specimin/SpeciminRunner.java @@ -9,6 +9,7 @@ import com.github.javaparser.symbolsolver.JavaSymbolSolver; import com.github.javaparser.symbolsolver.model.resolution.TypeSolver; import com.github.javaparser.symbolsolver.resolution.typesolvers.CombinedTypeSolver; +import com.github.javaparser.symbolsolver.resolution.typesolvers.JarTypeSolver; import com.github.javaparser.symbolsolver.resolution.typesolvers.JavaParserTypeSolver; import com.github.javaparser.symbolsolver.resolution.typesolvers.ReflectionTypeSolver; import java.io.File; @@ -41,6 +42,8 @@ public static void main(String... args) throws IOException { // for symbol resolution from source code and to organize the output directory. OptionSpec rootOption = optionParser.accepts("root").withRequiredArg(); + OptionSpec jarPath = optionParser.accepts("jarPath").withOptionalArg(); + // This option is the relative paths to the target file(s) - the .java file(s) containing // target method(s) - from the root. OptionSpec targetFilesOption = optionParser.accepts("targetFile").withRequiredArg(); @@ -57,11 +60,15 @@ public static void main(String... args) throws IOException { String root = options.valueOf(rootOption); List targetFiles = options.valuesOf(targetFilesOption); + List jarPaths = options.valuesOf(jarPath); // Set up the parser's symbol solver, so that we can resolve definitions. - TypeSolver typeSolver = + CombinedTypeSolver typeSolver = new CombinedTypeSolver( new ReflectionTypeSolver(), new JavaParserTypeSolver(new File(root))); + for (String path : jarPaths) { + typeSolver.add(new JarTypeSolver(path)); + } JavaSymbolSolver symbolSolver = new JavaSymbolSolver(typeSolver); StaticJavaParser.getConfiguration().setSymbolResolver(symbolSolver); @@ -72,6 +79,7 @@ public static void main(String... args) throws IOException { } UnsolvedSymbolVisitor addMissingClass = new UnsolvedSymbolVisitor(root); + addMissingClass.setThisSolver(jarPaths); /** * The set of path of files that have been created by addMissingClass. We will delete all those * files in the end. diff --git a/src/main/java/org/checkerframework/specimin/UnsolvedSymbolVisitor.java b/src/main/java/org/checkerframework/specimin/UnsolvedSymbolVisitor.java index eae9d567..5c595fb5 100644 --- a/src/main/java/org/checkerframework/specimin/UnsolvedSymbolVisitor.java +++ b/src/main/java/org/checkerframework/specimin/UnsolvedSymbolVisitor.java @@ -20,22 +20,19 @@ import com.github.javaparser.ast.visitor.ModifierVisitor; 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.ResolvedReferenceTypeDeclaration; import com.github.javaparser.resolution.types.ResolvedReferenceType; import com.github.javaparser.resolution.types.ResolvedType; +import com.github.javaparser.symbolsolver.resolution.typesolvers.CombinedTypeSolver; +import com.github.javaparser.symbolsolver.resolution.typesolvers.JarTypeSolver; import java.io.BufferedWriter; import java.io.FileWriter; import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.HashSet; -import java.util.Iterator; -import java.util.List; -import java.util.Map; -import java.util.Optional; -import java.util.Set; +import java.util.*; import org.checkerframework.checker.signature.qual.ClassGetSimpleName; import org.checkerframework.checker.signature.qual.DotSeparatedIdentifiers; import org.checkerframework.checker.signature.qual.FullyQualifiedName; @@ -101,6 +98,12 @@ public class UnsolvedSymbolVisitor extends ModifierVisitor { */ private String chosenPackage = ""; + private Set solversGroup = new HashSet<>(); + + private CombinedTypeSolver solver = new CombinedTypeSolver(); + + private Set classesFromJar = new HashSet<>(); + /** * Create a new UnsolvedSymbolVisitor instance * @@ -134,6 +137,15 @@ public void setImportStatement(NodeList listOfImports) { this.setclassAndPackageMap(); } + public void setThisSolver(List jarPaths) { + for (String path : jarPaths) { + try { + classesFromJar.addAll(new JarTypeSolver(path).getKnownClasses()); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + } /** * This method sets the classAndPackageMap. This method is called in the method * setImportStatement, as classAndPackageMap and importStatements should always be in sync. @@ -213,28 +225,34 @@ public Visitable visit(ClassOrInterfaceDeclaration node, Void arg) { @Override public Visitable visit(ExplicitConstructorInvocationStmt node, Void arg) { - NodeList arguments = node.getArguments(); - List parametersList = new ArrayList<>(); - for (Expression parameter : arguments) { - if (!canBeSolved(parameter)) { - return super.visit(node, arg); - } - ResolvedType type = parameter.calculateResolvedType(); - if (type instanceof PrimitiveType) { - parametersList.add(type.asPrimitive().name()); - } else if (type instanceof ReferenceType) { - parametersList.add(type.asReferenceType().getQualifiedName()); + try { + node.resolve().getQualifiedSignature(); + return super.visit(node, arg); + } catch (Exception e) { + NodeList arguments = node.getArguments(); + List parametersList = new ArrayList<>(); + for (Expression parameter : arguments) { + if (!canBeSolved(parameter)) { + return super.visit(node, arg); + } + ResolvedType type = parameter.calculateResolvedType(); + if (type instanceof PrimitiveType) { + parametersList.add(type.asPrimitive().name()); + } else if (type instanceof ReferenceType) { + parametersList.add(type.asReferenceType().getQualifiedName()); + } } + UnsolvedMethod constructorMethod = new UnsolvedMethod(this.parentClass, "", parametersList); + // if the parent class can not be found in the import statements, Specimin assumes it is in + // the + // same package as the child class, which is also the current class + UnsolvedClass parentClass = + new UnsolvedClass( + this.parentClass, classAndPackageMap.getOrDefault(this.parentClass, currentPackage)); + parentClass.addMethod(constructorMethod); + updateMissingClass(parentClass); + return super.visit(node, arg); } - UnsolvedMethod constructorMethod = new UnsolvedMethod(this.parentClass, "", parametersList); - // if the parent class can not be found in the import statements, Specimin assumes it is in the - // same package as the child class, which is also the current class - UnsolvedClass parentClass = - new UnsolvedClass( - this.parentClass, classAndPackageMap.getOrDefault(this.parentClass, currentPackage)); - parentClass.addMethod(constructorMethod); - updateMissingClass(parentClass); - return super.visit(node, arg); } @Override @@ -277,6 +295,10 @@ public Visitable visit(FieldAccessExpr node, Void p) { @Override public Visitable visit(MethodCallExpr method, Void p) { + if (canBeSolved(method) && isFromAJarFile(method)) { + updateClassesFromJarSourcesForMethodCall(method); + return super.visit(method, p); + } if (isASuperCall(method) && !canBeSolved(method)) { updateSyntheticClassForSuperCall(method); return super.visit(method, p); @@ -328,8 +350,10 @@ public Visitable visit(Parameter parameter, Void p) { public Visitable visit(ObjectCreationExpr newExpr, Void p) { SimpleName typeName = newExpr.getType().getName(); String type = typeName.asString(); - Expression asExpression = newExpr; - if (canBeSolved(asExpression)) { + if (canBeSolved(newExpr)) { + if (isFromAJarFile(newExpr)) { + updateClassesFromJarSourcesForObjectCreation(newExpr); + } return super.visit(newExpr, p); } this.gotException = true; @@ -391,7 +415,7 @@ public static String setInitialValueForVariableDeclaration( // simple form of that name @SuppressWarnings("signature") public static @ClassGetSimpleName String toSimpleName(@DotSeparatedIdentifiers String className) { - String[] elements = className.split("."); + String[] elements = className.split("[.]"); if (elements.length < 2) { return className; } @@ -436,6 +460,108 @@ public void updateUnsolvedClassWithMethodCall( } } + /** + * This method checks if an expression is solvable by JavaParser yet the source codes for that + * expression is not in the root directory. For example, if class A is solvable by JavaParser but + * A.java is not in the root directory, then this method will return true when taking class A as + * an input. This method is used to recognize elements from jar files. + * + * @param expr the expression to be checked + * @return true if expr is solvable and the source codes of expr is not in the root directory + */ + public boolean isFromAJarFile(Expression expr) { + String className; + if (expr instanceof MethodCallExpr) { + className = + ((MethodCallExpr) expr).resolve().getPackageName() + + "." + + ((MethodCallExpr) expr).resolve().getClassName(); + } else if (expr instanceof ObjectCreationExpr) { + String shortName = ((ObjectCreationExpr) expr).getTypeAsString(); + String packageName = classAndPackageMap.getOrDefault(shortName, this.chosenPackage); + className = packageName + "." + shortName; + } else { + throw new RuntimeException("Unexpected call: " + expr + ". Contact developers!"); + } + return classesFromJar.contains(className); + } + + /** + * This method updates a synthetic file based on a solvable expression. The input expression is + * solvable because its data is in the jar files that Specimin taks as input. + * + * @param expr the expression to be used + */ + @SuppressWarnings( + "signature") // the assumptions made here are not correct, since a @ClassGetSimpleName is not + // a @DotSeparatedIdentifiers + public void updateClassesFromJarSourcesForMethodCall(MethodCallExpr expr) { + if (!isFromAJarFile(expr)) { + throw new RuntimeException( + "Check with isFromAJarFile first before using updateClassesFromJarSources"); + } + String methodName = expr.getNameAsString(); + ResolvedMethodDeclaration methodSolved = expr.resolve(); + String className = methodSolved.getClassName(); + String packageName = methodSolved.getPackageName(); + String returnType = methodSolved.getReturnType().describe(); + List argumentsList = getArgumentsFromMethodCall(expr); + UnsolvedClass missingClass = new UnsolvedClass(className, packageName); + UnsolvedMethod thisMethod = new UnsolvedMethod(methodName, returnType, argumentsList); + missingClass.addMethod(thisMethod); + syntheticMethodAndClass.put(methodName, missingClass); + this.updateMissingClass(missingClass); + } + + /** + * This method updates a synthetic file based on a solvable expression. The input expression is + * solvable because its data is in the jar files that Specimin taks as input. + * + * @param expr the expression to be used + */ + @SuppressWarnings( + "signature") // the assumptions made here are not correct, since a @ClassGetSimpleName is not + // a @DotSeparatedIdentifiers + public void updateClassesFromJarSourcesForObjectCreation(ObjectCreationExpr expr) { + if (!isFromAJarFile(expr)) { + throw new RuntimeException( + "Check with isFromAJarFile first before using updateClassesFromJarSources"); + } + String objectName = expr.getType().getName().asString(); + ResolvedReferenceTypeDeclaration objectSolved = expr.resolve().declaringType(); + String className = objectSolved.getClassName(); + String packageName = objectSolved.getPackageName(); + List argumentsList = getArgumentsFromObjectCreation(expr); + UnsolvedClass missingClass = new UnsolvedClass(className, packageName); + UnsolvedMethod thisMethod = new UnsolvedMethod(objectName, "", argumentsList); + missingClass.addMethod(thisMethod); + this.updateMissingClass(missingClass); + } + + /** */ + public ResolvedReferenceTypeDeclaration getResolvedDeclarationFromJarForExpression( + Expression expr) { + String name; + if (expr instanceof MethodCallExpr) { + name = ((MethodCallExpr) expr).getNameAsString(); + } else if (expr instanceof ObjectCreationExpr) { + name = ((ObjectCreationExpr) expr).getTypeAsString(); + } else { + throw new RuntimeException("Unexpected call: " + expr + ". Contact developers!"); + } + String packageName = classAndPackageMap.getOrDefault(name, this.chosenPackage); + String fullName = packageName + "." + name; + for (JarTypeSolver solver : solversGroup) { + try { + return solver.solveType(fullName); + } catch (Exception e) { + } + } + throw new RuntimeException( + "Expression can't be solved. Make sure isFromJarFile returns true for this expression" + + " before using getResolvedDeclarationFromJarForMethodCall"); + } + /** * This method checks if an expression is called by the super keyword. For example, super.visit() * is such an expression. @@ -566,10 +692,10 @@ public static List getArgumentsFromObjectCreation(ObjectCreationExpr cre NodeList paraList = creationExpr.getArguments(); for (Expression parameter : paraList) { ResolvedType type = parameter.calculateResolvedType(); - if (type instanceof ResolvedReferenceType) { + if (type.isReferenceType()) { parametersList.add(((ResolvedReferenceType) type).getQualifiedName()); - } else if (type instanceof PrimitiveType) { - parametersList.add(type.asPrimitive().name()); + } else if (type.isPrimitive()) { + parametersList.add(type.describe()); } } return parametersList; @@ -601,7 +727,7 @@ public static boolean calledByAnUnsolvedSymbol(MethodCallExpr method) { */ public static boolean canBeSolved(Expression expr) { try { - expr.calculateResolvedType().describe(); + expr.calculateResolvedType(); return true; } catch (Exception e) { return false; diff --git a/src/test/java/org/checkerframework/specimin/CallJDK.java b/src/test/java/org/checkerframework/specimin/CallJDK.java index 26904464..e2f58e52 100644 --- a/src/test/java/org/checkerframework/specimin/CallJDK.java +++ b/src/test/java/org/checkerframework/specimin/CallJDK.java @@ -14,6 +14,7 @@ public void runTest() throws IOException { SpeciminTestExecutor.runTest( "callJDK", new String[] {"com/example/Simple.java"}, - new String[] {"com.example.Simple#test()"}); + new String[] {"com.example.Simple#test()"}, + new String[] {}); } } diff --git a/src/test/java/org/checkerframework/specimin/CallJavaParser.java b/src/test/java/org/checkerframework/specimin/CallJavaParser.java index e708d1b3..76fa51c2 100644 --- a/src/test/java/org/checkerframework/specimin/CallJavaParser.java +++ b/src/test/java/org/checkerframework/specimin/CallJavaParser.java @@ -14,6 +14,7 @@ public void runTest() throws IOException { SpeciminTestExecutor.runTest( "callJavaParser", new String[] {"com/example/Simple.java"}, - new String[] {"com.example.Simple#test()"}); + new String[] {"com.example.Simple#test()"}, + new String[] {}); } } diff --git a/src/test/java/org/checkerframework/specimin/CrossClassVariable.java b/src/test/java/org/checkerframework/specimin/CrossClassVariable.java index 3dce7e40..8609eb0c 100644 --- a/src/test/java/org/checkerframework/specimin/CrossClassVariable.java +++ b/src/test/java/org/checkerframework/specimin/CrossClassVariable.java @@ -13,6 +13,7 @@ public void runTest() throws IOException { SpeciminTestExecutor.runTest( "crossclassvariable", new String[] {"com/example/Foo.java", "com/example/Baz.java"}, - new String[] {"com.example.Foo#bar()"}); + new String[] {"com.example.Foo#bar()"}, + new String[] {}); } } diff --git a/src/test/java/org/checkerframework/specimin/GlobalVariables.java b/src/test/java/org/checkerframework/specimin/GlobalVariables.java index 560b04d7..f0b9c6ba 100644 --- a/src/test/java/org/checkerframework/specimin/GlobalVariables.java +++ b/src/test/java/org/checkerframework/specimin/GlobalVariables.java @@ -13,6 +13,7 @@ public void runTest() throws IOException { SpeciminTestExecutor.runTest( "globalvariables", new String[] {"com/example/Simple.java"}, - new String[] {"com.example.Simple#test()"}); + new String[] {"com.example.Simple#test()"}, + new String[] {}); } } diff --git a/src/test/java/org/checkerframework/specimin/HiddenTypeTest.java b/src/test/java/org/checkerframework/specimin/HiddenTypeTest.java index a6859d00..44ea4b47 100644 --- a/src/test/java/org/checkerframework/specimin/HiddenTypeTest.java +++ b/src/test/java/org/checkerframework/specimin/HiddenTypeTest.java @@ -14,6 +14,7 @@ public void runTest() throws IOException { SpeciminTestExecutor.runTest( "hiddenType", new String[] {"com/example/Simple.java"}, - new String[] {"com.example.Simple#isVoidType(MethodDeclaration)"}); + new String[] {"com.example.Simple#isVoidType(MethodDeclaration)"}, + new String[] {}); } } diff --git a/src/test/java/org/checkerframework/specimin/JarFileTest.java b/src/test/java/org/checkerframework/specimin/JarFileTest.java new file mode 100644 index 00000000..4f44d425 --- /dev/null +++ b/src/test/java/org/checkerframework/specimin/JarFileTest.java @@ -0,0 +1,16 @@ +package org.checkerframework.specimin; + +import java.io.IOException; +import org.junit.Test; + +/** This test checks if Specimin can handle jar files as input */ +public class JarFileTest { + @Test + public void runTest() throws IOException { + SpeciminTestExecutor.runTest( + "nodependenciesreturnssame", + new String[] {"com/example/Simple.java"}, + new String[] {"com.example.Simple#test()"}, + new String[] {"src/test/resources/jarfile/input/Book.jar"}); + } +} diff --git a/src/test/java/org/checkerframework/specimin/NoDependenciesReturnsSameTest.java b/src/test/java/org/checkerframework/specimin/NoDependenciesReturnsSameTest.java index aaeca268..d8161720 100644 --- a/src/test/java/org/checkerframework/specimin/NoDependenciesReturnsSameTest.java +++ b/src/test/java/org/checkerframework/specimin/NoDependenciesReturnsSameTest.java @@ -13,6 +13,7 @@ public void runTest() throws IOException { SpeciminTestExecutor.runTest( "nodependenciesreturnssame", new String[] {"com/example/Simple.java"}, - new String[] {"com.example.Simple#test()"}); + new String[] {"com.example.Simple#test()"}, + new String[] {}); } } diff --git a/src/test/java/org/checkerframework/specimin/OneFileSimpleTest.java b/src/test/java/org/checkerframework/specimin/OneFileSimpleTest.java index 53ebe91f..1464ea51 100644 --- a/src/test/java/org/checkerframework/specimin/OneFileSimpleTest.java +++ b/src/test/java/org/checkerframework/specimin/OneFileSimpleTest.java @@ -13,6 +13,7 @@ public void runTest() throws IOException { SpeciminTestExecutor.runTest( "onefilesimple", new String[] {"com/example/Simple.java"}, - new String[] {"com.example.Simple#bar()"}); + new String[] {"com.example.Simple#bar()"}, + new String[] {}); } } diff --git a/src/test/java/org/checkerframework/specimin/SameClassName.java b/src/test/java/org/checkerframework/specimin/SameClassName.java index 58d085aa..6fabf077 100644 --- a/src/test/java/org/checkerframework/specimin/SameClassName.java +++ b/src/test/java/org/checkerframework/specimin/SameClassName.java @@ -13,6 +13,7 @@ public void runTest() throws IOException { SpeciminTestExecutor.runTest( "sameclassname", new String[] {"com/example/Simple.java"}, - new String[] {"com.example.Simple#secondCalculator()"}); + new String[] {"com.example.Simple#secondCalculator()"}, + new String[] {}); } } diff --git a/src/test/java/org/checkerframework/specimin/SpeciminTestExecutor.java b/src/test/java/org/checkerframework/specimin/SpeciminTestExecutor.java index 58ef1d3e..7734ad5e 100644 --- a/src/test/java/org/checkerframework/specimin/SpeciminTestExecutor.java +++ b/src/test/java/org/checkerframework/specimin/SpeciminTestExecutor.java @@ -39,7 +39,8 @@ private SpeciminTestExecutor() { * class.fully.qualified.Name#methodName(Param1Type, Param2Type, ...) * @throws IOException if some operation fails */ - public static void runTest(String testName, String[] targetFiles, String[] targetMethods) + public static void runTest( + String testName, String[] targetFiles, String[] targetMethods, String[] jarPaths) throws IOException { // Create output directory Path outputDir = null; @@ -69,6 +70,10 @@ public static void runTest(String testName, String[] targetFiles, String[] targe speciminArgs.add("--targetMethod"); speciminArgs.add(targetMethod); } + for (String jarPath : jarPaths) { + speciminArgs.add("--jarPath"); + speciminArgs.add(jarPath); + } // Run specimin on target SpeciminRunner.main(speciminArgs.toArray(new String[0])); diff --git a/src/test/java/org/checkerframework/specimin/SuperClass.java b/src/test/java/org/checkerframework/specimin/SuperClass.java index e100dde9..7903c597 100644 --- a/src/test/java/org/checkerframework/specimin/SuperClass.java +++ b/src/test/java/org/checkerframework/specimin/SuperClass.java @@ -14,6 +14,7 @@ public void runTest() throws IOException { SpeciminTestExecutor.runTest( "superclass", new String[] {"com/example/Simple.java"}, - new String[] {"com.example.Simple#printMessage()"}); + new String[] {"com.example.Simple#printMessage()"}, + new String[] {}); } } diff --git a/src/test/java/org/checkerframework/specimin/SyntheticSuperConstructor.java b/src/test/java/org/checkerframework/specimin/SyntheticSuperConstructor.java index 30d0ebf2..1b90c3a1 100644 --- a/src/test/java/org/checkerframework/specimin/SyntheticSuperConstructor.java +++ b/src/test/java/org/checkerframework/specimin/SyntheticSuperConstructor.java @@ -13,6 +13,7 @@ public void runTest() throws IOException { SpeciminTestExecutor.runTest( "syntheticsuperconstructor", new String[] {"com/example/Car.java"}, - new String[] {"com.example.Car#Car()"}); + new String[] {"com.example.Car#Car()"}, + new String[] {}); } } diff --git a/src/test/java/org/checkerframework/specimin/SyntheticSuperMethod.java b/src/test/java/org/checkerframework/specimin/SyntheticSuperMethod.java index 9703d3b6..f515df49 100644 --- a/src/test/java/org/checkerframework/specimin/SyntheticSuperMethod.java +++ b/src/test/java/org/checkerframework/specimin/SyntheticSuperMethod.java @@ -13,6 +13,7 @@ public void runTest() throws IOException { SpeciminTestExecutor.runTest( "syntheticsupermethod", new String[] {"com/example/Car.java"}, - new String[] {"com.example.Car#getWheels()"}); + new String[] {"com.example.Car#getWheels()"}, + new String[] {}); } } diff --git a/src/test/java/org/checkerframework/specimin/SyntheticSuperVariables.java b/src/test/java/org/checkerframework/specimin/SyntheticSuperVariables.java index a3de7588..277ed7da 100644 --- a/src/test/java/org/checkerframework/specimin/SyntheticSuperVariables.java +++ b/src/test/java/org/checkerframework/specimin/SyntheticSuperVariables.java @@ -13,6 +13,7 @@ public void runTest() throws IOException { SpeciminTestExecutor.runTest( "syntheticsupervariables", new String[] {"com/example/Dog.java"}, - new String[] {"com.example.Dog#Dog()"}); + new String[] {"com.example.Dog#Dog()"}, + new String[] {}); } } diff --git a/src/test/java/org/checkerframework/specimin/ThreeLayerFunction.java b/src/test/java/org/checkerframework/specimin/ThreeLayerFunction.java index d0a52c7e..721ae764 100644 --- a/src/test/java/org/checkerframework/specimin/ThreeLayerFunction.java +++ b/src/test/java/org/checkerframework/specimin/ThreeLayerFunction.java @@ -15,6 +15,7 @@ public void runTest() throws IOException { SpeciminTestExecutor.runTest( "threelayerfunctioncall", new String[] {"com/example/Simple.java"}, - new String[] {"com.example.Simple#test()"}); + new String[] {"com.example.Simple#test()"}, + new String[] {}); } } diff --git a/src/test/java/org/checkerframework/specimin/TwoFileSimpleTest.java b/src/test/java/org/checkerframework/specimin/TwoFileSimpleTest.java index 6c620139..f146d7c4 100644 --- a/src/test/java/org/checkerframework/specimin/TwoFileSimpleTest.java +++ b/src/test/java/org/checkerframework/specimin/TwoFileSimpleTest.java @@ -14,6 +14,7 @@ public void runTest() throws IOException { SpeciminTestExecutor.runTest( "twofilesimple", new String[] {"com/example/Foo.java", "com/example/Baz.java"}, - new String[] {"com.example.Foo#bar()"}); + new String[] {"com.example.Foo#bar()"}, + new String[] {}); } } diff --git a/src/test/java/org/checkerframework/specimin/UnusedSecondClassTest.java b/src/test/java/org/checkerframework/specimin/UnusedSecondClassTest.java index 76b00d41..3240f4e4 100644 --- a/src/test/java/org/checkerframework/specimin/UnusedSecondClassTest.java +++ b/src/test/java/org/checkerframework/specimin/UnusedSecondClassTest.java @@ -13,6 +13,7 @@ public void runTest() throws IOException { SpeciminTestExecutor.runTest( "unusedsecondclass", new String[] {"com/example/Foo.java", "com/example/Baz.java"}, - new String[] {"com.example.Foo#bar()"}); + new String[] {"com.example.Foo#bar()"}, + new String[] {}); } } diff --git a/src/test/resources/jarfile/expected/an/old/library/Book.java b/src/test/resources/jarfile/expected/an/old/library/Book.java new file mode 100644 index 00000000..f8254949 --- /dev/null +++ b/src/test/resources/jarfile/expected/an/old/library/Book.java @@ -0,0 +1,11 @@ +package an.old.library; +public class Book { + + public Book(int parameter0) { + throw new Error(); + } + + public java.lang.String getRates() { + throw new Error(); + } +} diff --git a/src/test/resources/jarfile/expected/com/example/Simple.java b/src/test/resources/jarfile/expected/com/example/Simple.java new file mode 100644 index 00000000..aa145f6f --- /dev/null +++ b/src/test/resources/jarfile/expected/com/example/Simple.java @@ -0,0 +1,11 @@ +package com.example; + +import an.old.library.Book; + +class Simple { + // Target method. + int test() { + Book bookOfTheYear = new Book(2023); + return bookOfTheYear.getRates().length(); + } +} diff --git a/src/test/resources/jarfile/input/com/example/Simple.java b/src/test/resources/jarfile/input/com/example/Simple.java new file mode 100644 index 00000000..aa145f6f --- /dev/null +++ b/src/test/resources/jarfile/input/com/example/Simple.java @@ -0,0 +1,11 @@ +package com.example; + +import an.old.library.Book; + +class Simple { + // Target method. + int test() { + Book bookOfTheYear = new Book(2023); + return bookOfTheYear.getRates().length(); + } +} From 85fa746e0334fd146819d53604bfeae58ef03cb5 Mon Sep 17 00:00:00 2001 From: unknown Date: Mon, 24 Jul 2023 15:16:45 -0400 Subject: [PATCH 13/31] remove wildcards --- .../checkerframework/specimin/UnsolvedSymbolVisitor.java | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/main/java/org/checkerframework/specimin/UnsolvedSymbolVisitor.java b/src/main/java/org/checkerframework/specimin/UnsolvedSymbolVisitor.java index 5c595fb5..f8b6efce 100644 --- a/src/main/java/org/checkerframework/specimin/UnsolvedSymbolVisitor.java +++ b/src/main/java/org/checkerframework/specimin/UnsolvedSymbolVisitor.java @@ -32,7 +32,14 @@ import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; -import java.util.*; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.Set; import org.checkerframework.checker.signature.qual.ClassGetSimpleName; import org.checkerframework.checker.signature.qual.DotSeparatedIdentifiers; import org.checkerframework.checker.signature.qual.FullyQualifiedName; From 24f15bb80b2b19b11bb3eb47225e20eef8de7636 Mon Sep 17 00:00:00 2001 From: unknown Date: Mon, 24 Jul 2023 15:25:35 -0400 Subject: [PATCH 14/31] delete unused methods, revise javadoc --- .../specimin/UnsolvedSymbolVisitor.java | 36 ++----------------- 1 file changed, 3 insertions(+), 33 deletions(-) diff --git a/src/main/java/org/checkerframework/specimin/UnsolvedSymbolVisitor.java b/src/main/java/org/checkerframework/specimin/UnsolvedSymbolVisitor.java index f8b6efce..bdbb8dc0 100644 --- a/src/main/java/org/checkerframework/specimin/UnsolvedSymbolVisitor.java +++ b/src/main/java/org/checkerframework/specimin/UnsolvedSymbolVisitor.java @@ -24,7 +24,6 @@ import com.github.javaparser.resolution.declarations.ResolvedReferenceTypeDeclaration; import com.github.javaparser.resolution.types.ResolvedReferenceType; import com.github.javaparser.resolution.types.ResolvedType; -import com.github.javaparser.symbolsolver.resolution.typesolvers.CombinedTypeSolver; import com.github.javaparser.symbolsolver.resolution.typesolvers.JarTypeSolver; import java.io.BufferedWriter; import java.io.FileWriter; @@ -105,10 +104,7 @@ public class UnsolvedSymbolVisitor extends ModifierVisitor { */ private String chosenPackage = ""; - private Set solversGroup = new HashSet<>(); - - private CombinedTypeSolver solver = new CombinedTypeSolver(); - + /** This set has classes that come from jar files input */ private Set classesFromJar = new HashSet<>(); /** @@ -469,12 +465,10 @@ public void updateUnsolvedClassWithMethodCall( /** * This method checks if an expression is solvable by JavaParser yet the source codes for that - * expression is not in the root directory. For example, if class A is solvable by JavaParser but - * A.java is not in the root directory, then this method will return true when taking class A as - * an input. This method is used to recognize elements from jar files. + * expression is not in the root directory. * * @param expr the expression to be checked - * @return true if expr is solvable and the source codes of expr is not in the root directory + * @return true if expr is from jar files */ public boolean isFromAJarFile(Expression expr) { String className; @@ -545,30 +539,6 @@ public void updateClassesFromJarSourcesForObjectCreation(ObjectCreationExpr expr this.updateMissingClass(missingClass); } - /** */ - public ResolvedReferenceTypeDeclaration getResolvedDeclarationFromJarForExpression( - Expression expr) { - String name; - if (expr instanceof MethodCallExpr) { - name = ((MethodCallExpr) expr).getNameAsString(); - } else if (expr instanceof ObjectCreationExpr) { - name = ((ObjectCreationExpr) expr).getTypeAsString(); - } else { - throw new RuntimeException("Unexpected call: " + expr + ". Contact developers!"); - } - String packageName = classAndPackageMap.getOrDefault(name, this.chosenPackage); - String fullName = packageName + "." + name; - for (JarTypeSolver solver : solversGroup) { - try { - return solver.solveType(fullName); - } catch (Exception e) { - } - } - throw new RuntimeException( - "Expression can't be solved. Make sure isFromJarFile returns true for this expression" - + " before using getResolvedDeclarationFromJarForMethodCall"); - } - /** * This method checks if an expression is called by the super keyword. For example, super.visit() * is such an expression. From 8799764c0f2abd261656094bb85332f916b5747b Mon Sep 17 00:00:00 2001 From: unknown Date: Mon, 24 Jul 2023 15:30:38 -0400 Subject: [PATCH 15/31] clarify method names --- .../checkerframework/specimin/UnsolvedSymbolVisitor.java | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/main/java/org/checkerframework/specimin/UnsolvedSymbolVisitor.java b/src/main/java/org/checkerframework/specimin/UnsolvedSymbolVisitor.java index bdbb8dc0..e736f570 100644 --- a/src/main/java/org/checkerframework/specimin/UnsolvedSymbolVisitor.java +++ b/src/main/java/org/checkerframework/specimin/UnsolvedSymbolVisitor.java @@ -140,7 +140,12 @@ public void setImportStatement(NodeList listOfImports) { this.setclassAndPackageMap(); } - public void setThisSolver(List jarPaths) { + /** + * This method sets the value of classesFromJar based on the known class of jar type solvers + * + * @param jarPaths + */ + public void setClassesFromJar(List jarPaths) { for (String path : jarPaths) { try { classesFromJar.addAll(new JarTypeSolver(path).getKnownClasses()); From 2b48170c80fd1ec33f3c7bc6c9f890ba87777042 Mon Sep 17 00:00:00 2001 From: unknown Date: Mon, 24 Jul 2023 15:38:51 -0400 Subject: [PATCH 16/31] correct javadoc for isFromAJarFile --- .../org/checkerframework/specimin/UnsolvedSymbolVisitor.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/org/checkerframework/specimin/UnsolvedSymbolVisitor.java b/src/main/java/org/checkerframework/specimin/UnsolvedSymbolVisitor.java index e736f570..1bb89262 100644 --- a/src/main/java/org/checkerframework/specimin/UnsolvedSymbolVisitor.java +++ b/src/main/java/org/checkerframework/specimin/UnsolvedSymbolVisitor.java @@ -469,8 +469,8 @@ public void updateUnsolvedClassWithMethodCall( } /** - * This method checks if an expression is solvable by JavaParser yet the source codes for that - * expression is not in the root directory. + * This method checks if an expression is solvable by JavaParser because its source codes is from + * the jar files input. * * @param expr the expression to be checked * @return true if expr is from jar files From 8346f4f3314267716a7090bbf7b6cbdbe5966146 Mon Sep 17 00:00:00 2001 From: Loi Nguyen <113363230+LoiNguyenCS@users.noreply.github.com> Date: Mon, 24 Jul 2023 15:41:55 -0400 Subject: [PATCH 17/31] add instruction for jarPath --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index 18824124..7035c041 100644 --- a/README.md +++ b/README.md @@ -26,6 +26,8 @@ The available options are (required options in **bold**, repeatable options in * * ***--targetFile***: a source file in which to search for target methods * ***--targetMethod***: a target method that must be preserved, and whose dependencies should be stubbed out. Use the format `class.fully.qualified.Name#methodName(Param1Type, Param2Type, ...)` * **--outputDirectory**: the directory in which to place the output. The directory must be writeable and will be created if it does not exist. +* *--jarPath*: the path of a Jar file for Specimin to take as input. + Options may be specified in any order. When supplying repeatable options more than once, the option must be repeated for each value. From 970349cbfc4f7e1139082359965e603f3f69269a Mon Sep 17 00:00:00 2001 From: unknown Date: Mon, 24 Jul 2023 15:47:51 -0400 Subject: [PATCH 18/31] add jar file --- src/test/resources/jarfile/input/Book.jar | Bin 0 -> 753 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 src/test/resources/jarfile/input/Book.jar diff --git a/src/test/resources/jarfile/input/Book.jar b/src/test/resources/jarfile/input/Book.jar new file mode 100644 index 0000000000000000000000000000000000000000..e4731a30f23f8963ea3b1d1d499184eb3c4e356f GIT binary patch literal 753 zcmWIWW@Zs#;Nak3==1s<#()Gk8CV#6T|*poJ^kGD|D9rBU}gyLX6FE@V1g=yR?d4^j!i!gC{yo;bU5&l?eFP7t!t|sRi)M4bk z?()nzAuPXD^gqAqx$wq4Sv+?KpN+is2hP|)wN*3JT<2vi6hFB$sGKERu~qHDf?m^k zMoX6+yZ&)w--HX77ER)BDfm&I6Y_G;nZC%o@-E4F;#pdp2MzR}Uj1V?^;g&uR^zum zVdnS$#o7PLZ#EAqww*YA=fvG=7Z(OTY*04&ae0}A&6&qGriT|^NPY9Mn?f10@Rtumv*VT9Fb7vI(FF fMs^q|f)QXVkcl(81H4(;KuVc`@Cc9=2YUbj@^0qU literal 0 HcmV?d00001 From eb2e149cf153f667fd6808468f48e117b2016c2f Mon Sep 17 00:00:00 2001 From: unknown Date: Tue, 25 Jul 2023 15:40:49 -0400 Subject: [PATCH 19/31] fix method call --- src/main/java/org/checkerframework/specimin/SpeciminRunner.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/org/checkerframework/specimin/SpeciminRunner.java b/src/main/java/org/checkerframework/specimin/SpeciminRunner.java index 4dcd5096..64b8ee1b 100644 --- a/src/main/java/org/checkerframework/specimin/SpeciminRunner.java +++ b/src/main/java/org/checkerframework/specimin/SpeciminRunner.java @@ -79,7 +79,7 @@ public static void main(String... args) throws IOException { } UnsolvedSymbolVisitor addMissingClass = new UnsolvedSymbolVisitor(root); - addMissingClass.setThisSolver(jarPaths); + addMissingClass.setClassesFromJar(jarPaths); /** * The set of path of files that have been created by addMissingClass. We will delete all those * files in the end. From de3c350665bed58d63a45d12bb164ce26e1254d2 Mon Sep 17 00:00:00 2001 From: unknown Date: Tue, 25 Jul 2023 15:50:36 -0400 Subject: [PATCH 20/31] add type qualifiers for class name --- JavaParser.astub | 7 +++++++ .../checkerframework/specimin/UnsolvedSymbolVisitor.java | 4 ++-- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/JavaParser.astub b/JavaParser.astub index 24a4f762..375c823b 100644 --- a/JavaParser.astub +++ b/JavaParser.astub @@ -22,4 +22,11 @@ package com.github.javaparser.resolution.declarations; interface ResolvedMethodDeclaration { @DotSeparatedIdentifiers String getClassName(); +} + +package com.github.javaparser.symbolsolver.resolution.typesolvers; + +class JarTypeSolver { + // this method lists all the names of classes solved by JarTypeSolver. All the names are in fully-qualified names. + Set<@FullyQualifiedName String> getKnownClasses(); } \ No newline at end of file diff --git a/src/main/java/org/checkerframework/specimin/UnsolvedSymbolVisitor.java b/src/main/java/org/checkerframework/specimin/UnsolvedSymbolVisitor.java index 1bb89262..12043292 100644 --- a/src/main/java/org/checkerframework/specimin/UnsolvedSymbolVisitor.java +++ b/src/main/java/org/checkerframework/specimin/UnsolvedSymbolVisitor.java @@ -104,8 +104,8 @@ public class UnsolvedSymbolVisitor extends ModifierVisitor { */ private String chosenPackage = ""; - /** This set has classes that come from jar files input */ - private Set classesFromJar = new HashSet<>(); + /** This set has fully-qualified class names that come from jar files input */ + private Set<@FullyQualifiedName String> classesFromJar = new HashSet<>(); /** * Create a new UnsolvedSymbolVisitor instance From 88298fb08de0561e107b8a2bb3252bd8a47c28d2 Mon Sep 17 00:00:00 2001 From: unknown Date: Tue, 25 Jul 2023 16:30:26 -0400 Subject: [PATCH 21/31] fix javadoc --- .../specimin/UnsolvedSymbolVisitor.java | 37 +++++++++++-------- 1 file changed, 22 insertions(+), 15 deletions(-) diff --git a/src/main/java/org/checkerframework/specimin/UnsolvedSymbolVisitor.java b/src/main/java/org/checkerframework/specimin/UnsolvedSymbolVisitor.java index 12043292..600ce687 100644 --- a/src/main/java/org/checkerframework/specimin/UnsolvedSymbolVisitor.java +++ b/src/main/java/org/checkerframework/specimin/UnsolvedSymbolVisitor.java @@ -234,6 +234,7 @@ public Visitable visit(ClassOrInterfaceDeclaration node, Void arg) { @Override public Visitable visit(ExplicitConstructorInvocationStmt node, Void arg) { try { + // check if the symbol is solvable. If it is, then there's no need to create a synthetic file. node.resolve().getQualifiedSignature(); return super.visit(node, arg); } catch (Exception e) { @@ -252,8 +253,7 @@ public Visitable visit(ExplicitConstructorInvocationStmt node, Void arg) { } UnsolvedMethod constructorMethod = new UnsolvedMethod(this.parentClass, "", parametersList); // if the parent class can not be found in the import statements, Specimin assumes it is in - // the - // same package as the child class, which is also the current class + // the same package as the child class. UnsolvedClass parentClass = new UnsolvedClass( this.parentClass, classAndPackageMap.getOrDefault(this.parentClass, currentPackage)); @@ -469,11 +469,12 @@ public void updateUnsolvedClassWithMethodCall( } /** - * This method checks if an expression is solvable by JavaParser because its source codes is from - * the jar files input. + * Checks if the given expression is solvable because the class file containing its symbol is + * found in one of the jar files provided via the {@code --jarPath} option. * - * @param expr the expression to be checked - * @return true if expr is from jar files + * @param expr The expression to be checked for solvability. + * @return true iff the expression is solvable because its class file was found in one of the jar + * files. */ public boolean isFromAJarFile(Expression expr) { String className; @@ -498,9 +499,6 @@ public boolean isFromAJarFile(Expression expr) { * * @param expr the expression to be used */ - @SuppressWarnings( - "signature") // the assumptions made here are not correct, since a @ClassGetSimpleName is not - // a @DotSeparatedIdentifiers public void updateClassesFromJarSourcesForMethodCall(MethodCallExpr expr) { if (!isFromAJarFile(expr)) { throw new RuntimeException( @@ -508,9 +506,17 @@ public void updateClassesFromJarSourcesForMethodCall(MethodCallExpr expr) { } String methodName = expr.getNameAsString(); ResolvedMethodDeclaration methodSolved = expr.resolve(); - String className = methodSolved.getClassName(); + @SuppressWarnings( + "signature") // this is not a precise assumption, as getClassName() will return a + // @FullyQualifiedName if the class is not of primitive type. However, this is + // favorable, since we don't have to write any additional import statements. + @ClassGetSimpleName String className = methodSolved.getClassName(); String packageName = methodSolved.getPackageName(); - String returnType = methodSolved.getReturnType().describe(); + @SuppressWarnings( + "signature") // this is not a precise assumption, as getReturnType().describe() will return + // a @FullyQualifiedName if the class is not of primitive type. However, this + // is favorable, since we don't have to write any additional import statements. + @ClassGetSimpleName String returnType = methodSolved.getReturnType().describe(); List argumentsList = getArgumentsFromMethodCall(expr); UnsolvedClass missingClass = new UnsolvedClass(className, packageName); UnsolvedMethod thisMethod = new UnsolvedMethod(methodName, returnType, argumentsList); @@ -525,9 +531,6 @@ public void updateClassesFromJarSourcesForMethodCall(MethodCallExpr expr) { * * @param expr the expression to be used */ - @SuppressWarnings( - "signature") // the assumptions made here are not correct, since a @ClassGetSimpleName is not - // a @DotSeparatedIdentifiers public void updateClassesFromJarSourcesForObjectCreation(ObjectCreationExpr expr) { if (!isFromAJarFile(expr)) { throw new RuntimeException( @@ -535,7 +538,11 @@ public void updateClassesFromJarSourcesForObjectCreation(ObjectCreationExpr expr } String objectName = expr.getType().getName().asString(); ResolvedReferenceTypeDeclaration objectSolved = expr.resolve().declaringType(); - String className = objectSolved.getClassName(); + @SuppressWarnings( + "signature") // this is not a precise assumption, as getClassName() will return a + // @FullyQualifiedName if the class is not of primitive type. However, this is + // favorable, since we don't have to write any additional import statements. + @ClassGetSimpleName String className = objectSolved.getClassName(); String packageName = objectSolved.getPackageName(); List argumentsList = getArgumentsFromObjectCreation(expr); UnsolvedClass missingClass = new UnsolvedClass(className, packageName); From ed8f1dce7d99fea839b43cfb800257b7bfbe30c4 Mon Sep 17 00:00:00 2001 From: unknown Date: Tue, 25 Jul 2023 16:42:14 -0400 Subject: [PATCH 22/31] clarify the path in readme --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 7035c041..8a363135 100644 --- a/README.md +++ b/README.md @@ -26,7 +26,7 @@ The available options are (required options in **bold**, repeatable options in * * ***--targetFile***: a source file in which to search for target methods * ***--targetMethod***: a target method that must be preserved, and whose dependencies should be stubbed out. Use the format `class.fully.qualified.Name#methodName(Param1Type, Param2Type, ...)` * **--outputDirectory**: the directory in which to place the output. The directory must be writeable and will be created if it does not exist. -* *--jarPath*: the path of a Jar file for Specimin to take as input. +* *--jarPath*: the absolute path of a Jar file for Specimin to take as input. Options may be specified in any order. When supplying repeatable options more than once, the option must be repeated for each value. From 3f1256b589427a1ee51f28332460dd4ba99ac5fa Mon Sep 17 00:00:00 2001 From: unknown Date: Wed, 26 Jul 2023 09:35:25 -0400 Subject: [PATCH 23/31] add runTestWithoutJarPaths --- .../org/checkerframework/specimin/CallJDK.java | 5 ++--- .../checkerframework/specimin/CallJavaParser.java | 5 ++--- .../specimin/CrossClassVariable.java | 5 ++--- .../specimin/GlobalVariables.java | 5 ++--- .../checkerframework/specimin/HiddenTypeTest.java | 5 ++--- .../specimin/NoDependenciesReturnsSameTest.java | 5 ++--- .../specimin/OneFileSimpleTest.java | 5 ++--- .../checkerframework/specimin/SameClassName.java | 5 ++--- .../specimin/SpeciminTestExecutor.java | 15 +++++++++++++++ .../org/checkerframework/specimin/SuperClass.java | 5 ++--- .../specimin/SyntheticSuperConstructor.java | 5 ++--- .../specimin/SyntheticSuperMethod.java | 5 ++--- .../specimin/SyntheticSuperVariables.java | 5 ++--- .../specimin/ThreeLayerFunction.java | 5 ++--- .../specimin/TwoFileSimpleTest.java | 5 ++--- .../specimin/UnusedSecondClassTest.java | 5 ++--- 16 files changed, 45 insertions(+), 45 deletions(-) diff --git a/src/test/java/org/checkerframework/specimin/CallJDK.java b/src/test/java/org/checkerframework/specimin/CallJDK.java index e2f58e52..d358618a 100644 --- a/src/test/java/org/checkerframework/specimin/CallJDK.java +++ b/src/test/java/org/checkerframework/specimin/CallJDK.java @@ -11,10 +11,9 @@ public class CallJDK { @Test public void runTest() throws IOException { - SpeciminTestExecutor.runTest( + SpeciminTestExecutor.runTestWithoutJarPaths( "callJDK", new String[] {"com/example/Simple.java"}, - new String[] {"com.example.Simple#test()"}, - new String[] {}); + new String[] {"com.example.Simple#test()"}); } } diff --git a/src/test/java/org/checkerframework/specimin/CallJavaParser.java b/src/test/java/org/checkerframework/specimin/CallJavaParser.java index 76fa51c2..ff4f9665 100644 --- a/src/test/java/org/checkerframework/specimin/CallJavaParser.java +++ b/src/test/java/org/checkerframework/specimin/CallJavaParser.java @@ -11,10 +11,9 @@ public class CallJavaParser { @Test public void runTest() throws IOException { - SpeciminTestExecutor.runTest( + SpeciminTestExecutor.runTestWithoutJarPaths( "callJavaParser", new String[] {"com/example/Simple.java"}, - new String[] {"com.example.Simple#test()"}, - new String[] {}); + new String[] {"com.example.Simple#test()"}); } } diff --git a/src/test/java/org/checkerframework/specimin/CrossClassVariable.java b/src/test/java/org/checkerframework/specimin/CrossClassVariable.java index 8609eb0c..74c283ba 100644 --- a/src/test/java/org/checkerframework/specimin/CrossClassVariable.java +++ b/src/test/java/org/checkerframework/specimin/CrossClassVariable.java @@ -10,10 +10,9 @@ public class CrossClassVariable { @Test public void runTest() throws IOException { - SpeciminTestExecutor.runTest( + SpeciminTestExecutor.runTestWithoutJarPaths( "crossclassvariable", new String[] {"com/example/Foo.java", "com/example/Baz.java"}, - new String[] {"com.example.Foo#bar()"}, - new String[] {}); + new String[] {"com.example.Foo#bar()"}); } } diff --git a/src/test/java/org/checkerframework/specimin/GlobalVariables.java b/src/test/java/org/checkerframework/specimin/GlobalVariables.java index f0b9c6ba..ad4fb70b 100644 --- a/src/test/java/org/checkerframework/specimin/GlobalVariables.java +++ b/src/test/java/org/checkerframework/specimin/GlobalVariables.java @@ -10,10 +10,9 @@ public class GlobalVariables { @Test public void runTest() throws IOException { - SpeciminTestExecutor.runTest( + SpeciminTestExecutor.runTestWithoutJarPaths( "globalvariables", new String[] {"com/example/Simple.java"}, - new String[] {"com.example.Simple#test()"}, - new String[] {}); + new String[] {"com.example.Simple#test()"}); } } diff --git a/src/test/java/org/checkerframework/specimin/HiddenTypeTest.java b/src/test/java/org/checkerframework/specimin/HiddenTypeTest.java index 44ea4b47..024f5a6a 100644 --- a/src/test/java/org/checkerframework/specimin/HiddenTypeTest.java +++ b/src/test/java/org/checkerframework/specimin/HiddenTypeTest.java @@ -11,10 +11,9 @@ public class HiddenTypeTest { @Test public void runTest() throws IOException { - SpeciminTestExecutor.runTest( + SpeciminTestExecutor.runTestWithoutJarPaths( "hiddenType", new String[] {"com/example/Simple.java"}, - new String[] {"com.example.Simple#isVoidType(MethodDeclaration)"}, - new String[] {}); + new String[] {"com.example.Simple#isVoidType(MethodDeclaration)"}); } } diff --git a/src/test/java/org/checkerframework/specimin/NoDependenciesReturnsSameTest.java b/src/test/java/org/checkerframework/specimin/NoDependenciesReturnsSameTest.java index d8161720..ff11ddd5 100644 --- a/src/test/java/org/checkerframework/specimin/NoDependenciesReturnsSameTest.java +++ b/src/test/java/org/checkerframework/specimin/NoDependenciesReturnsSameTest.java @@ -10,10 +10,9 @@ public class NoDependenciesReturnsSameTest { @Test public void runTest() throws IOException { - SpeciminTestExecutor.runTest( + SpeciminTestExecutor.runTestWithoutJarPaths( "nodependenciesreturnssame", new String[] {"com/example/Simple.java"}, - new String[] {"com.example.Simple#test()"}, - new String[] {}); + new String[] {"com.example.Simple#test()"}); } } diff --git a/src/test/java/org/checkerframework/specimin/OneFileSimpleTest.java b/src/test/java/org/checkerframework/specimin/OneFileSimpleTest.java index 1464ea51..231c10f8 100644 --- a/src/test/java/org/checkerframework/specimin/OneFileSimpleTest.java +++ b/src/test/java/org/checkerframework/specimin/OneFileSimpleTest.java @@ -10,10 +10,9 @@ public class OneFileSimpleTest { @Test public void runTest() throws IOException { - SpeciminTestExecutor.runTest( + SpeciminTestExecutor.runTestWithoutJarPaths( "onefilesimple", new String[] {"com/example/Simple.java"}, - new String[] {"com.example.Simple#bar()"}, - new String[] {}); + new String[] {"com.example.Simple#bar()"}); } } diff --git a/src/test/java/org/checkerframework/specimin/SameClassName.java b/src/test/java/org/checkerframework/specimin/SameClassName.java index 6fabf077..5b801af1 100644 --- a/src/test/java/org/checkerframework/specimin/SameClassName.java +++ b/src/test/java/org/checkerframework/specimin/SameClassName.java @@ -10,10 +10,9 @@ public class SameClassName { @Test public void runTest() throws IOException { - SpeciminTestExecutor.runTest( + SpeciminTestExecutor.runTestWithoutJarPaths( "sameclassname", new String[] {"com/example/Simple.java"}, - new String[] {"com.example.Simple#secondCalculator()"}, - new String[] {}); + new String[] {"com.example.Simple#secondCalculator()"}); } } diff --git a/src/test/java/org/checkerframework/specimin/SpeciminTestExecutor.java b/src/test/java/org/checkerframework/specimin/SpeciminTestExecutor.java index 7734ad5e..7df76b8a 100644 --- a/src/test/java/org/checkerframework/specimin/SpeciminTestExecutor.java +++ b/src/test/java/org/checkerframework/specimin/SpeciminTestExecutor.java @@ -37,6 +37,7 @@ private SpeciminTestExecutor() { * @param targetFiles the targeted files * @param targetMethods the targeted methods, each in the format * class.fully.qualified.Name#methodName(Param1Type, Param2Type, ...) + * @param jarPaths the path of jar files for Specimin to solve symbols * @throws IOException if some operation fails */ public static void runTest( @@ -123,6 +124,20 @@ public static void runTest( exitCode); } + /** + * This method call the method runTest without an array of jar paths. + * + * @param testName the name of the test folder + * @param targetFiles the targeted files + * @param targetMethods the targeted methods, each in the format + * class.fully.qualified.Name#methodName(Param1Type, Param2Type, ...) + * @throws IOException if some operation fails + */ + public static void runTestWithoutJarPaths( + String testName, String[] targetFiles, String[] targetMethods) throws IOException { + runTest(testName, targetFiles, targetMethods, new String[] {}); + } + /** Code borrowed from https://www.baeldung.com/run-shell-command-in-java. */ private static class StreamGobbler implements Runnable { private InputStream inputStream; diff --git a/src/test/java/org/checkerframework/specimin/SuperClass.java b/src/test/java/org/checkerframework/specimin/SuperClass.java index 7903c597..0666d657 100644 --- a/src/test/java/org/checkerframework/specimin/SuperClass.java +++ b/src/test/java/org/checkerframework/specimin/SuperClass.java @@ -11,10 +11,9 @@ public class SuperClass { @Test public void runTest() throws IOException { - SpeciminTestExecutor.runTest( + SpeciminTestExecutor.runTestWithoutJarPaths( "superclass", new String[] {"com/example/Simple.java"}, - new String[] {"com.example.Simple#printMessage()"}, - new String[] {}); + new String[] {"com.example.Simple#printMessage()"}); } } diff --git a/src/test/java/org/checkerframework/specimin/SyntheticSuperConstructor.java b/src/test/java/org/checkerframework/specimin/SyntheticSuperConstructor.java index 1b90c3a1..57ef01f3 100644 --- a/src/test/java/org/checkerframework/specimin/SyntheticSuperConstructor.java +++ b/src/test/java/org/checkerframework/specimin/SyntheticSuperConstructor.java @@ -10,10 +10,9 @@ public class SyntheticSuperConstructor { @Test public void runTest() throws IOException { - SpeciminTestExecutor.runTest( + SpeciminTestExecutor.runTestWithoutJarPaths( "syntheticsuperconstructor", new String[] {"com/example/Car.java"}, - new String[] {"com.example.Car#Car()"}, - new String[] {}); + new String[] {"com.example.Car#Car()"}); } } diff --git a/src/test/java/org/checkerframework/specimin/SyntheticSuperMethod.java b/src/test/java/org/checkerframework/specimin/SyntheticSuperMethod.java index f515df49..35e0c2e2 100644 --- a/src/test/java/org/checkerframework/specimin/SyntheticSuperMethod.java +++ b/src/test/java/org/checkerframework/specimin/SyntheticSuperMethod.java @@ -10,10 +10,9 @@ public class SyntheticSuperMethod { @Test public void runTest() throws IOException { - SpeciminTestExecutor.runTest( + SpeciminTestExecutor.runTestWithoutJarPaths( "syntheticsupermethod", new String[] {"com/example/Car.java"}, - new String[] {"com.example.Car#getWheels()"}, - new String[] {}); + new String[] {"com.example.Car#getWheels()"}); } } diff --git a/src/test/java/org/checkerframework/specimin/SyntheticSuperVariables.java b/src/test/java/org/checkerframework/specimin/SyntheticSuperVariables.java index 277ed7da..f553f0a8 100644 --- a/src/test/java/org/checkerframework/specimin/SyntheticSuperVariables.java +++ b/src/test/java/org/checkerframework/specimin/SyntheticSuperVariables.java @@ -10,10 +10,9 @@ public class SyntheticSuperVariables { @Test public void runTest() throws IOException { - SpeciminTestExecutor.runTest( + SpeciminTestExecutor.runTestWithoutJarPaths( "syntheticsupervariables", new String[] {"com/example/Dog.java"}, - new String[] {"com.example.Dog#Dog()"}, - new String[] {}); + new String[] {"com.example.Dog#Dog()"}); } } diff --git a/src/test/java/org/checkerframework/specimin/ThreeLayerFunction.java b/src/test/java/org/checkerframework/specimin/ThreeLayerFunction.java index 721ae764..0be1a086 100644 --- a/src/test/java/org/checkerframework/specimin/ThreeLayerFunction.java +++ b/src/test/java/org/checkerframework/specimin/ThreeLayerFunction.java @@ -12,10 +12,9 @@ public class ThreeLayerFunction { @Test public void runTest() throws IOException { - SpeciminTestExecutor.runTest( + SpeciminTestExecutor.runTestWithoutJarPaths( "threelayerfunctioncall", new String[] {"com/example/Simple.java"}, - new String[] {"com.example.Simple#test()"}, - new String[] {}); + new String[] {"com.example.Simple#test()"}); } } diff --git a/src/test/java/org/checkerframework/specimin/TwoFileSimpleTest.java b/src/test/java/org/checkerframework/specimin/TwoFileSimpleTest.java index f146d7c4..19b8e0b8 100644 --- a/src/test/java/org/checkerframework/specimin/TwoFileSimpleTest.java +++ b/src/test/java/org/checkerframework/specimin/TwoFileSimpleTest.java @@ -11,10 +11,9 @@ public class TwoFileSimpleTest { @Test public void runTest() throws IOException { - SpeciminTestExecutor.runTest( + SpeciminTestExecutor.runTestWithoutJarPaths( "twofilesimple", new String[] {"com/example/Foo.java", "com/example/Baz.java"}, - new String[] {"com.example.Foo#bar()"}, - new String[] {}); + new String[] {"com.example.Foo#bar()"}); } } diff --git a/src/test/java/org/checkerframework/specimin/UnusedSecondClassTest.java b/src/test/java/org/checkerframework/specimin/UnusedSecondClassTest.java index 3240f4e4..2f3ced98 100644 --- a/src/test/java/org/checkerframework/specimin/UnusedSecondClassTest.java +++ b/src/test/java/org/checkerframework/specimin/UnusedSecondClassTest.java @@ -10,10 +10,9 @@ public class UnusedSecondClassTest { @Test public void runTest() throws IOException { - SpeciminTestExecutor.runTest( + SpeciminTestExecutor.runTestWithoutJarPaths( "unusedsecondclass", new String[] {"com/example/Foo.java", "com/example/Baz.java"}, - new String[] {"com.example.Foo#bar()"}, - new String[] {}); + new String[] {"com.example.Foo#bar()"}); } } From c951730e120c340bb9b96b52cea872b677996680 Mon Sep 17 00:00:00 2001 From: unknown Date: Wed, 26 Jul 2023 15:52:43 -0400 Subject: [PATCH 24/31] clarify ambiguity --- .../specimin/UnsolvedClass.java | 22 ++++++++--------- .../specimin/UnsolvedSymbolVisitor.java | 24 +++++++++---------- 2 files changed, 23 insertions(+), 23 deletions(-) diff --git a/src/main/java/org/checkerframework/specimin/UnsolvedClass.java b/src/main/java/org/checkerframework/specimin/UnsolvedClass.java index 6ac375a1..9f59835f 100644 --- a/src/main/java/org/checkerframework/specimin/UnsolvedClass.java +++ b/src/main/java/org/checkerframework/specimin/UnsolvedClass.java @@ -15,8 +15,8 @@ public class UnsolvedClass { /** The name of the class */ private final @ClassGetSimpleName String className; - /** The variables of this class */ - private final Set classVariables; + /** The fields of this class */ + private final Set classFields; /** * The name of the package of the class. We rely on the import statements from the source codes to @@ -34,7 +34,7 @@ public UnsolvedClass(@ClassGetSimpleName String className, String packageName) { this.className = className; this.methods = new HashSet<>(); this.packageName = packageName; - this.classVariables = new HashSet<>(); + this.classFields = new HashSet<>(); } /** @@ -65,12 +65,12 @@ public String getPackageName() { } /** - * Get the variables of this current class + * Get the fields of this current class * * @return classVariables */ - public Set getClassVariables() { - return classVariables; + public Set getClassFields() { + return classFields; } /** @@ -83,13 +83,13 @@ public void addMethod(UnsolvedMethod method) { } /** - * Add variables expression to the class. We expect something like "int i" or "String y" instead - * of just "i" and "y" + * Add field declaration to the class. We expect something like "int i" or "String y" instead of + * just "i" and "y" * * @param variableExpression the expression of the variables to be added */ - public void addVariables(String variableExpression) { - this.classVariables.add(variableExpression); + public void addFields(String variableExpression) { + this.classFields.add(variableExpression); } /** @@ -118,7 +118,7 @@ public String toString() { StringBuilder sb = new StringBuilder(); sb.append("package ").append(packageName).append(";\n"); sb.append("public class ").append(className).append(" {\n"); - for (String variableDeclarations : classVariables) { + for (String variableDeclarations : classFields) { sb.append(" " + variableDeclarations + ";\n"); } for (UnsolvedMethod method : methods) { diff --git a/src/main/java/org/checkerframework/specimin/UnsolvedSymbolVisitor.java b/src/main/java/org/checkerframework/specimin/UnsolvedSymbolVisitor.java index eae9d567..c87e497f 100644 --- a/src/main/java/org/checkerframework/specimin/UnsolvedSymbolVisitor.java +++ b/src/main/java/org/checkerframework/specimin/UnsolvedSymbolVisitor.java @@ -49,8 +49,8 @@ public class UnsolvedSymbolVisitor extends ModifierVisitor { /** - * The parent class of this current class file. If there is no parent class, then the value of - * this variable is an empty string + * The unsolved parent class of this current class file. If there is no unsolved parent class, + * then the value of this variable is an empty string */ private @ClassGetSimpleName String parentClass = ""; @@ -482,13 +482,13 @@ public void updateSyntheticClassForSuperCall(Expression expr) { } /** - * This method will add a new variable declaration to a synthetic class. This class is mainly used - * for unsolved parent class. The declaration of the variable in the parent class will be the same - * as the declaration in the child class since Specimin does not have access to much information. - * If the variable is not found in the child class, Specimin will create a synthetic class to be - * the type of that variable. + * This method will add a new field declaration to a synthetic class. This class is mainly used + * for unsolved parent class. The declaration of the field in the parent class will be the same as + * the declaration in the child class since Specimin does not have access to much information. If + * the field is not found in the child class, Specimin will create a synthetic class to be the + * type of that field. * - * @param var the variable to be added + * @param var the field to be added * @param className the name of the synthetic class * @param packageName the package of the synthetic class */ @@ -497,7 +497,7 @@ public void updateUnsolvedClassWithVariables( UnsolvedClass relatedClass = new UnsolvedClass(className, packageName); if (variablesAndDeclaration.containsKey(var)) { String variableExpression = variablesAndDeclaration.get(var); - relatedClass.addVariables(variableExpression); + relatedClass.addFields(variableExpression); updateMissingClass(relatedClass); } else { // since it is just simple string combination, it is a simple name @@ -506,7 +506,7 @@ public void updateUnsolvedClassWithVariables( UnsolvedClass varType = new UnsolvedClass(variableType, packageName); String variableExpression = String.format("%s %s = new %s();", variableType, var, variableType); - relatedClass.addVariables(variableExpression); + relatedClass.addFields(variableExpression); updateMissingClass(relatedClass); updateMissingClass(varType); } @@ -698,8 +698,8 @@ public void updateMissingClass(UnsolvedClass missedClass) { e.addMethod(method); } } - for (String variablesDescription : missedClass.getClassVariables()) { - e.addVariables(variablesDescription); + for (String variablesDescription : missedClass.getClassFields()) { + e.addFields(variablesDescription); } return; } From 86de3cbb48ffb68f95260ef16d1ef4e1fb4fd92c Mon Sep 17 00:00:00 2001 From: unknown Date: Wed, 26 Jul 2023 16:07:39 -0400 Subject: [PATCH 25/31] variables to fields --- .../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 c23a0428..950905c7 100644 --- a/src/main/java/org/checkerframework/specimin/UnsolvedSymbolVisitor.java +++ b/src/main/java/org/checkerframework/specimin/UnsolvedSymbolVisitor.java @@ -589,7 +589,7 @@ public void updateSyntheticClassForSuperCall(Expression expr) { this.parentClass, methodAndReturnType.getOrDefault(expr.asMethodCallExpr().getNameAsString(), "")); } else { - updateUnsolvedClassWithVariables( + updateUnsolvedClassWithFields( expr.asFieldAccessExpr().getNameAsString(), parentClass, classAndPackageMap.getOrDefault(parentClass, this.currentPackage)); @@ -597,7 +597,7 @@ public void updateSyntheticClassForSuperCall(Expression expr) { } /** - * This method will add a new field declaration to a synthetic class. This class is mainly used + * This method will add a new field declaration to a synthetic class. This method is mainly used * for unsolved parent class. The declaration of the field in the parent class will be the same as * the declaration in the child class since Specimin does not have access to much information. If * the field is not found in the child class, Specimin will create a synthetic class to be the @@ -607,7 +607,7 @@ public void updateSyntheticClassForSuperCall(Expression expr) { * @param className the name of the synthetic class * @param packageName the package of the synthetic class */ - public void updateUnsolvedClassWithVariables( + public void updateUnsolvedClassWithFields( String var, @ClassGetSimpleName String className, String packageName) { UnsolvedClass relatedClass = new UnsolvedClass(className, packageName); if (variablesAndDeclaration.containsKey(var)) { From 5b142c3d0a6c0de6f275b9f83d62e735cececdf8 Mon Sep 17 00:00:00 2001 From: unknown Date: Wed, 26 Jul 2023 16:24:15 -0400 Subject: [PATCH 26/31] some more cleaning --- .../checkerframework/specimin/TargetMethodFinderVisitor.java | 1 + .../syntheticsupervariables/expected/com/example/Dog.java | 2 +- .../syntheticsupervariables/input/com/example/Dog.java | 2 +- 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/main/java/org/checkerframework/specimin/TargetMethodFinderVisitor.java b/src/main/java/org/checkerframework/specimin/TargetMethodFinderVisitor.java index b262b692..c469c3e2 100644 --- a/src/main/java/org/checkerframework/specimin/TargetMethodFinderVisitor.java +++ b/src/main/java/org/checkerframework/specimin/TargetMethodFinderVisitor.java @@ -142,6 +142,7 @@ public Visitable visit(ClassOrInterfaceDeclaration decl, Void p) { @Override public Visitable visit(ConstructorDeclaration method, Void p) { String constructorMethodAsString = method.getDeclarationAsString(false, false, false); + // the methodName will be something like this: "com.example.Car#Car()" String methodName = this.classFQName + "#" diff --git a/src/test/resources/syntheticsupervariables/expected/com/example/Dog.java b/src/test/resources/syntheticsupervariables/expected/com/example/Dog.java index fc77ebdd..12062ce5 100644 --- a/src/test/resources/syntheticsupervariables/expected/com/example/Dog.java +++ b/src/test/resources/syntheticsupervariables/expected/com/example/Dog.java @@ -4,7 +4,7 @@ public class Dog extends Mammal { String habitat; boolean canBreathUnderWater; - public void Dog() { + public void setUp() { habitat = super.habitat; canBreathUnderWater = super.canBreathUnderWater; diff --git a/src/test/resources/syntheticsupervariables/input/com/example/Dog.java b/src/test/resources/syntheticsupervariables/input/com/example/Dog.java index fc77ebdd..12062ce5 100644 --- a/src/test/resources/syntheticsupervariables/input/com/example/Dog.java +++ b/src/test/resources/syntheticsupervariables/input/com/example/Dog.java @@ -4,7 +4,7 @@ public class Dog extends Mammal { String habitat; boolean canBreathUnderWater; - public void Dog() { + public void setUp() { habitat = super.habitat; canBreathUnderWater = super.canBreathUnderWater; From 88d9ed35583706230822d19df9a223bada622612 Mon Sep 17 00:00:00 2001 From: unknown Date: Wed, 26 Jul 2023 16:27:00 -0400 Subject: [PATCH 27/31] fix wrong name in test file --- .../org/checkerframework/specimin/SyntheticSuperVariables.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/test/java/org/checkerframework/specimin/SyntheticSuperVariables.java b/src/test/java/org/checkerframework/specimin/SyntheticSuperVariables.java index f553f0a8..e60b3e2f 100644 --- a/src/test/java/org/checkerframework/specimin/SyntheticSuperVariables.java +++ b/src/test/java/org/checkerframework/specimin/SyntheticSuperVariables.java @@ -13,6 +13,6 @@ public void runTest() throws IOException { SpeciminTestExecutor.runTestWithoutJarPaths( "syntheticsupervariables", new String[] {"com/example/Dog.java"}, - new String[] {"com.example.Dog#Dog()"}); + new String[] {"com.example.Dog#setUp()"}); } } From d6d4b9987065ed477233a97dade32d4cbd82cda3 Mon Sep 17 00:00:00 2001 From: unknown Date: Wed, 26 Jul 2023 17:43:08 -0400 Subject: [PATCH 28/31] change checks for synthetic return type --- .../specimin/SpeciminRunner.java | 2 +- .../specimin/UnsolvedSymbolVisitor.java | 26 +++++++++++++++++++ 2 files changed, 27 insertions(+), 1 deletion(-) diff --git a/src/main/java/org/checkerframework/specimin/SpeciminRunner.java b/src/main/java/org/checkerframework/specimin/SpeciminRunner.java index 64b8ee1b..576a1424 100644 --- a/src/main/java/org/checkerframework/specimin/SpeciminRunner.java +++ b/src/main/java/org/checkerframework/specimin/SpeciminRunner.java @@ -162,7 +162,7 @@ public static void main(String... args) throws IOException { for (Entry target : parsedTargetFiles.entrySet()) { // If a compilation output's entire body has been removed, do not output it. if (isEmptyCompilationUnit(target.getValue())) { - boolean isASyntheticReturnType = target.getKey().contains("ReturnType"); + boolean isASyntheticReturnType = addMissingClass.isASyntheticReturnType(target.getKey()); boolean isASyntheticSuperClass = !addMissingClass.getParentClass().equals("") && target.getKey().contains(addMissingClass.getParentClass()); diff --git a/src/main/java/org/checkerframework/specimin/UnsolvedSymbolVisitor.java b/src/main/java/org/checkerframework/specimin/UnsolvedSymbolVisitor.java index 950905c7..ed78f645 100644 --- a/src/main/java/org/checkerframework/specimin/UnsolvedSymbolVisitor.java +++ b/src/main/java/org/checkerframework/specimin/UnsolvedSymbolVisitor.java @@ -107,6 +107,12 @@ public class UnsolvedSymbolVisitor extends ModifierVisitor { /** This set has fully-qualified class names that come from jar files input */ private Set<@FullyQualifiedName String> classesFromJar = new HashSet<>(); + /** + * This set has the fully-qualfied name of the synthetic return types created by this instance of + * UnsolvedSymbolVisitor + */ + private Set syntheticReturnTypes = new HashSet<>(); + /** * Create a new UnsolvedSymbolVisitor instance * @@ -213,6 +219,16 @@ public void setExceptionToFalse() { gotException = false; } + /** + * Check if a class is a synthetic return type created by this instance of UnsolvedSymbolVisitor + * + * @param className the name of the class to be checked + * @return true if the class is a synthetic return type created by this UnsolvedSymbolVisitor + */ + public boolean isASyntheticReturnType(String className) { + return syntheticReturnTypes.contains(className); + } + @Override public Visitable visit(PackageDeclaration node, Void arg) { this.currentPackage = node.getNameAsString(); @@ -460,6 +476,11 @@ public void updateUnsolvedClassWithMethodCall( syntheticMethodAndClass.put(methodName, missingClass); this.updateMissingClass(missingClass); if (desiredReturnType.equals("")) { + @SuppressWarnings( + "signature") // returnType is a @ClassGetSimpleName, so combining it with the package will + // give us the fully-qualified name + @FullyQualifiedName String packageName = missingClass.getPackageName() + "." + returnType; + syntheticReturnTypes.add(packageName); UnsolvedClass returnTypeForThisMethod = new UnsolvedClass(returnType, missingClass.getPackageName()); this.updateMissingClass(returnTypeForThisMethod); @@ -994,6 +1015,11 @@ public void updateClassSetWithNotSimpleMethodCall(MethodCallExpr method) { new UnsolvedMethod(methodName, thisReturnType, getArgumentsFromMethodCall(method)); newClass.addMethod(newMethod); syntheticMethodAndClass.put(newMethod.toString(), newClass); + @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); } From 6a98ef22e6816c98a2d22a7fd658699de27d6a88 Mon Sep 17 00:00:00 2001 From: unknown Date: Wed, 26 Jul 2023 18:21:58 -0400 Subject: [PATCH 29/31] remove indexOf operation --- .../checkerframework/specimin/TargetMethodFinderVisitor.java | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/main/java/org/checkerframework/specimin/TargetMethodFinderVisitor.java b/src/main/java/org/checkerframework/specimin/TargetMethodFinderVisitor.java index c469c3e2..828456e1 100644 --- a/src/main/java/org/checkerframework/specimin/TargetMethodFinderVisitor.java +++ b/src/main/java/org/checkerframework/specimin/TargetMethodFinderVisitor.java @@ -143,10 +143,7 @@ public Visitable visit(ClassOrInterfaceDeclaration decl, Void p) { public Visitable visit(ConstructorDeclaration method, Void p) { String constructorMethodAsString = method.getDeclarationAsString(false, false, false); // the methodName will be something like this: "com.example.Car#Car()" - String methodName = - this.classFQName - + "#" - + constructorMethodAsString.substring(constructorMethodAsString.indexOf(' ') + 1); + String methodName = this.classFQName + "#" + constructorMethodAsString; if (this.targetMethodNames.contains(methodName)) { insideTargetMethod = true; targetMethods.add(method.resolve().getQualifiedSignature()); From d567f0f135647df5822af5d8615a156e91a8fc91 Mon Sep 17 00:00:00 2001 From: unknown Date: Wed, 26 Jul 2023 19:11:53 -0400 Subject: [PATCH 30/31] change parent class to superclass --- .../specimin/SpeciminRunner.java | 4 +- .../specimin/UnsolvedSymbolVisitor.java | 49 ++++++++++--------- 2 files changed, 27 insertions(+), 26 deletions(-) diff --git a/src/main/java/org/checkerframework/specimin/SpeciminRunner.java b/src/main/java/org/checkerframework/specimin/SpeciminRunner.java index 576a1424..667d3c3b 100644 --- a/src/main/java/org/checkerframework/specimin/SpeciminRunner.java +++ b/src/main/java/org/checkerframework/specimin/SpeciminRunner.java @@ -164,8 +164,8 @@ public static void main(String... args) throws IOException { if (isEmptyCompilationUnit(target.getValue())) { boolean isASyntheticReturnType = addMissingClass.isASyntheticReturnType(target.getKey()); boolean isASyntheticSuperClass = - !addMissingClass.getParentClass().equals("") - && target.getKey().contains(addMissingClass.getParentClass()); + !addMissingClass.getSuperClass().equals("") + && target.getKey().contains(addMissingClass.getSuperClass()); if (!isASyntheticSuperClass && !isASyntheticReturnType) { continue; } diff --git a/src/main/java/org/checkerframework/specimin/UnsolvedSymbolVisitor.java b/src/main/java/org/checkerframework/specimin/UnsolvedSymbolVisitor.java index ed78f645..f1628415 100644 --- a/src/main/java/org/checkerframework/specimin/UnsolvedSymbolVisitor.java +++ b/src/main/java/org/checkerframework/specimin/UnsolvedSymbolVisitor.java @@ -52,10 +52,10 @@ public class UnsolvedSymbolVisitor extends ModifierVisitor { /** - * The unsolved parent class of this current class file. If there is no unsolved parent class, - * then the value of this variable is an empty string + * The unsolved superclass of this current class file. If there is no unsolved superclass, then + * the value of this variable is an empty string */ - private @ClassGetSimpleName String parentClass = ""; + private @ClassGetSimpleName String superClass = ""; /** The package of this class */ private String currentPackage = ""; @@ -185,12 +185,12 @@ private void setclassAndPackageMap() { } /** - * Get the value of parentclass + * Get the value of superClass * - * @return parentClass the value of parentClass + * @return superClass the value of superClass */ - public String getParentClass() { - return parentClass; + public String getSuperClass() { + return superClass; } /** @@ -239,10 +239,11 @@ public Visitable visit(PackageDeclaration node, Void arg) { public Visitable visit(ClassOrInterfaceDeclaration node, Void arg) { if (node.getExtendedTypes().isNonEmpty()) { // note that since Specimin does not have access to the class paths of the project, all the - // unsolved methods related to inheritance will be placed in the parent class, even if there + // unsolved methods related to inheritance will be placed in the parent class (the nearest + // superclass), even if there // is a grandparent class and so forth. - SimpleName parentClassSimpleName = node.getExtendedTypes().get(0).getName(); - parentClass = parentClassSimpleName.asString(); + SimpleName superClassSimpleName = node.getExtendedTypes().get(0).getName(); + superClass = superClassSimpleName.asString(); } return super.visit(node, arg); } @@ -267,14 +268,14 @@ public Visitable visit(ExplicitConstructorInvocationStmt node, Void arg) { parametersList.add(type.asReferenceType().getQualifiedName()); } } - UnsolvedMethod constructorMethod = new UnsolvedMethod(this.parentClass, "", parametersList); - // if the parent class can not be found in the import statements, Specimin assumes it is in + UnsolvedMethod constructorMethod = new UnsolvedMethod(this.superClass, "", parametersList); + // if the superclass can not be found in the import statements, Specimin assumes it is in // the same package as the child class. - UnsolvedClass parentClass = + UnsolvedClass superClass = new UnsolvedClass( - this.parentClass, classAndPackageMap.getOrDefault(this.parentClass, currentPackage)); - parentClass.addMethod(constructorMethod); - updateMissingClass(parentClass); + this.superClass, classAndPackageMap.getOrDefault(this.superClass, currentPackage)); + superClass.addMethod(constructorMethod); + updateMissingClass(superClass); return super.visit(node, arg); } } @@ -607,22 +608,22 @@ public void updateSyntheticClassForSuperCall(Expression expr) { if (expr instanceof MethodCallExpr) { updateUnsolvedClassWithMethodCall( expr.asMethodCallExpr(), - this.parentClass, + this.superClass, methodAndReturnType.getOrDefault(expr.asMethodCallExpr().getNameAsString(), "")); } else { updateUnsolvedClassWithFields( expr.asFieldAccessExpr().getNameAsString(), - parentClass, - classAndPackageMap.getOrDefault(parentClass, this.currentPackage)); + superClass, + classAndPackageMap.getOrDefault(superClass, this.currentPackage)); } } /** - * This method will add a new field declaration to a synthetic class. This method is mainly used - * for unsolved parent class. The declaration of the field in the parent class will be the same as - * the declaration in the child class since Specimin does not have access to much information. If - * the field is not found in the child class, Specimin will create a synthetic class to be the - * type of that field. + * This method will add a new field declaration to a synthetic class. This method is intended to + * be used for unsolved superclass. The declaration of the field in the superclass will be the + * same as the declaration in the child class since Specimin does not have access to much + * information. If the field is not found in the child class, Specimin will create a synthetic + * class to be the type of that field. * * @param var the field to be added * @param className the name of the synthetic class From b467e6d1ec6a9401f2f1111f55413d30849f26b9 Mon Sep 17 00:00:00 2001 From: unknown Date: Thu, 27 Jul 2023 15:22:17 -0400 Subject: [PATCH 31/31] add final keyword --- .../specimin/UnsolvedSymbolVisitor.java | 25 +++++++------------ 1 file changed, 9 insertions(+), 16 deletions(-) diff --git a/src/main/java/org/checkerframework/specimin/UnsolvedSymbolVisitor.java b/src/main/java/org/checkerframework/specimin/UnsolvedSymbolVisitor.java index f1628415..2d31c731 100644 --- a/src/main/java/org/checkerframework/specimin/UnsolvedSymbolVisitor.java +++ b/src/main/java/org/checkerframework/specimin/UnsolvedSymbolVisitor.java @@ -63,22 +63,22 @@ public class UnsolvedSymbolVisitor extends ModifierVisitor { /** * This map will map the name of variables in the current class and its corresponding declaration */ - private Map variablesAndDeclaration; + private final Map variablesAndDeclaration = new HashMap<>(); /** * Based on the method declarations in the current class, this map will map the name of the * methods with their corresponding return types */ - private Map methodAndReturnType; + private final Map methodAndReturnType = new HashMap<>(); /** List of classes not in the source codes */ - private Set missingClass; + private final Set missingClass = new HashSet<>(); /** The same as the root being used in SpeciminRunner */ private String rootDirectory; /** This instance maps the name of a synthetic method with its synthetic class */ - private Map syntheticMethodAndClass; + private final Map syntheticMethodAndClass = new HashMap<>(); /** * This is to check if the current synthetic files are enough to prevent UnsolvedSymbolException @@ -90,13 +90,13 @@ public class UnsolvedSymbolVisitor extends ModifierVisitor { * The list of classes that have been created. We use this list to delete all the temporary * synthetic classes when Specimin finishes its run */ - private Set createdClass; + private final Set createdClass = new HashSet<>(); /** List of import statement from the current compilation unit that is being visited */ - private List importStatement; + private List importStatement = new ArrayList<>(); /** This map the classes in the compilation unit with the related package */ - private Map classAndPackageMap; + private final Map classAndPackageMap = new HashMap<>(); /** * If there is any import statement that ends with *, this string will be replaced by one of the @@ -105,13 +105,13 @@ public class UnsolvedSymbolVisitor extends ModifierVisitor { private String chosenPackage = ""; /** This set has fully-qualified class names that come from jar files input */ - private Set<@FullyQualifiedName String> classesFromJar = new HashSet<>(); + private final Set<@FullyQualifiedName String> classesFromJar = new HashSet<>(); /** * This set has the fully-qualfied name of the synthetic return types created by this instance of * UnsolvedSymbolVisitor */ - private Set syntheticReturnTypes = new HashSet<>(); + private final Set syntheticReturnTypes = new HashSet<>(); /** * Create a new UnsolvedSymbolVisitor instance @@ -120,14 +120,7 @@ public class UnsolvedSymbolVisitor extends ModifierVisitor { */ public UnsolvedSymbolVisitor(String rootDirectory) { this.rootDirectory = rootDirectory; - this.missingClass = new HashSet<>(); this.gotException = true; - this.importStatement = new ArrayList<>(); - this.classAndPackageMap = new HashMap<>(); - this.createdClass = new HashSet<>(); - this.syntheticMethodAndClass = new HashMap<>(); - this.methodAndReturnType = new HashMap<>(); - this.variablesAndDeclaration = new HashMap<>(); } /**