Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

Add FieldDeclarationVisitor #30

Merged
merged 8 commits into from
Oct 31, 2023
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
package org.checkerframework.specimin;

import com.github.javaparser.ast.body.ClassOrInterfaceDeclaration;
import com.github.javaparser.ast.body.FieldDeclaration;
import com.github.javaparser.ast.body.VariableDeclarator;
import com.github.javaparser.ast.expr.SimpleName;
import com.github.javaparser.ast.visitor.VoidVisitorAdapter;
import java.util.HashMap;
import java.util.Map;
import org.checkerframework.checker.signature.qual.ClassGetSimpleName;

/**
* This visitor is designed to assist the UnsolvedSymbolVisitor in creating synthetic files for
* unsolved NameExpr instances by listing all the names of declared fields in the current input
* file. It's important to note that this visitor is intended to be used in conjunction with the
* UnsolvedSymbolVisitor, so both visitors will traverse the same Java file.
* Thus, @ClassGetSimpleName names for involved classes should be sufficient.
*/
public class FieldDeclarationsVisitor extends VoidVisitorAdapter<Void> {
/**
* A mapping of field names to the @ClassGetSimpleName name of the classes in which they are
* declared. Since inner classes can be involved, a map is used instead of a simple list.
*/
Map<String, @ClassGetSimpleName String> fieldAndItsClass;

/** Constructs a new FieldDeclarationsVisitor. */
public FieldDeclarationsVisitor() {
fieldAndItsClass = new HashMap<>();
}

@Override
public void visit(FieldDeclaration decl, Void p) {
ClassOrInterfaceDeclaration classNode =
(ClassOrInterfaceDeclaration) decl.getParentNode().get();
SimpleName classNodeSimpleName = classNode.getName();
String className = classNodeSimpleName.asString();
for (VariableDeclarator var : decl.getVariables()) {
fieldAndItsClass.put(var.getNameAsString(), className);
}
}

/**
* Get the value of fieldAndItsClass
*
* @return the value of fieldAndItsClass
*/
public Map<String, @ClassGetSimpleName String> getFieldAndItsClass() {
return fieldAndItsClass;
}
}
19 changes: 11 additions & 8 deletions src/main/java/org/checkerframework/specimin/SpeciminRunner.java
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@
import java.io.PrintWriter;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
Expand All @@ -26,7 +25,6 @@
import joptsimple.OptionParser;
import joptsimple.OptionSet;
import joptsimple.OptionSpec;
import org.checkerframework.checker.signature.qual.ClassGetSimpleName;

/** This class is the main runner for Specimin. Use its main() method to start Specimin. */
public class SpeciminRunner {
Expand Down Expand Up @@ -113,6 +111,11 @@ public static void performMinimization(
addMissingClass.setExceptionToFalse();
for (CompilationUnit cu : parsedTargetFiles.values()) {
addMissingClass.setImportStatement(cu.getImports());
// it's important to make sure that getDeclarations and addMissingClass will visit the same
// file for each execution of the loop
FieldDeclarationsVisitor getDeclarations = new FieldDeclarationsVisitor();
cu.accept(getDeclarations, null);
addMissingClass.setFieldAndItsClass(getDeclarations.getFieldAndItsClass());
cu.accept(addMissingClass, null);
}
addMissingClass.updateSyntheticSourceCode();
Expand Down Expand Up @@ -177,13 +180,13 @@ public static void performMinimization(
cu.accept(methodPruner, null);
}
for (Entry<String, CompilationUnit> target : parsedTargetFiles.entrySet()) {
// If a compilation output's entire body has been removed, do not output it.
// If a compilation output's entire body has been removed and the related class is not used by
// the target methods, do not output it.
if (isEmptyCompilationUnit(target.getValue())) {
boolean isASyntheticReturnType = addMissingClass.isASyntheticReturnType(target.getKey());
Collection<@ClassGetSimpleName String> listOfSuperClass = addMissingClass.getSuperClass();
boolean isASyntheticSuperClass =
!listOfSuperClass.isEmpty() && listOfSuperClass.contains(target.getKey());
if (!isASyntheticSuperClass && !isASyntheticReturnType) {
// target key will have this form: "path/of/package/ClassName.java"
String classFullyQualfiedName = target.getKey().replace("/", ".");
classFullyQualfiedName = classFullyQualfiedName.replace(".java", "");
if (!finder.getUsedClass().contains(classFullyQualfiedName)) {
continue;
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@
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.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.declarations.ResolvedFieldDeclaration;
Expand Down Expand Up @@ -181,6 +183,10 @@ public Visitable visit(MethodDeclaration method, Void p) {
insideTargetMethod = true;
targetMethods.add(method.resolve().getQualifiedSignature());
unfoundMethods.remove(methodName);
Type returnType = method.getType();
if (returnType instanceof ReferenceType) {
usedClass.add(returnType.resolve().asReferenceType().getQualifiedName());
}
}
Visitable result = super.visit(method, p);
insideTargetMethod = false;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,13 @@ public class UnsolvedSymbolVisitor extends ModifierVisitor<Void> {
* type of fields in the parent class of the current class.
*/
private final Set<String> syntheticTypes = new HashSet<>();

/**
* A mapping of field names to the @ClassGetSimpleName name of the classes in which they are
* declared.
*/
private Map<String, @ClassGetSimpleName String> fieldAndItsClass = new HashMap<>();

/**
* Create a new UnsolvedSymbolVisitor instance
*
Expand Down Expand Up @@ -191,6 +198,16 @@ private void setclassAndPackageMap() {
}
}

/**
* This method sets up the value of fieldsAndItsClass by using the result obtained from
* FieldDeclarationsVisitor
*
* @param fieldAndItsClass the value of fieldsAndItsClass from FieldDeclarationsVisitor
*/
public void setFieldAndItsClass(Map<String, @ClassGetSimpleName String> fieldAndItsClass) {
this.fieldAndItsClass = fieldAndItsClass;
}

/**
* Get the collection of superclasses. Due to the potential presence of inner classes, this method
* returns a collection, as there can be multiple superclasses involved in a single file.
Expand Down Expand Up @@ -227,16 +244,6 @@ 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();
Expand Down Expand Up @@ -293,13 +300,22 @@ public Visitable visit(ExplicitConstructorInvocationStmt node, Void arg) {

@Override
public Visitable visit(NameExpr node, Void arg) {
// This method explicitly handles NameExpr instances that represent fields of classes but are
// not explicitly shown in the code. For example, if "number" is a field of a class, then
// "return number;" is an expression that this method will address. If the NameExpr instance is
// not a field of any class, or if it is a field of a class but is explicitly referenced, such
// as "Math.number," we handle it in other visit methods.
if (!canBeSolved(node)) {
Optional<Node> parentNode = node.getParentNode();
// we take care of the two latter cases in other visit methods
// we take care of MethodCallExpr and FieldAccessExpr cases in other visit methods
if (parentNode.isEmpty()
|| !(parentNode.get() instanceof MethodCallExpr
|| parentNode.get() instanceof FieldAccessExpr)) {
updateSyntheticClassForSuperCall(node);
String fieldName = node.getNameAsString();
if (!fieldAndItsClass.containsKey(fieldName)
|| !fieldAndItsClass.get(fieldName).equals(className)) {
updateSyntheticClassForSuperCall(node);
}
}
}
return super.visit(node, arg);
Expand Down Expand Up @@ -336,6 +352,15 @@ public Visitable visit(MethodDeclaration node, Void arg) {
@DotSeparatedIdentifiers String nodeTypeAsString = nodeType.asString();
@ClassGetSimpleName String nodeTypeSimpleForm = toSimpleName(nodeTypeAsString);
methodAndReturnType.put(node.getNameAsString(), nodeTypeSimpleForm);
try {
nodeType.resolve();
} catch (UnsolvedSymbolException | UnsupportedOperationException e) {
UnsolvedClass syntheticType =
new UnsolvedClass(
nodeTypeSimpleForm,
classAndPackageMap.getOrDefault(nodeTypeSimpleForm, this.chosenPackage));
this.updateMissingClass(syntheticType);
}
return super.visit(node, arg);
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package org.checkerframework.specimin;

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

/**
* This test checks if Specimin can correctly differentiate between an unsolved field of the current
* class and an implicitly accessed super variable. Both scenarios may appear as unsolved NameExpr
* instances in JavaParser. This test ensures that Specimin can correctly identify and handle these
* distinct cases.
*/
public class HiddenSuperVariables {
@Test
public void runTest() throws IOException {
SpeciminTestExecutor.runTestWithoutJarPaths(
"hiddenSuperVariables",
new String[] {"com/example/Simple.java"},
new String[] {"com.example.Simple#getMyInteger()", "com.example.Simple#getCorrect()"});
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package com.example;

import com.library.number.SimpleInt;

public class Simple extends Simplicity {

private SimpleInt myInteger = null;

public SimpleInt getMyInteger() {
return myInteger;
}

public boolean getCorrect() {
return correct;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package com.example;

public class Simplicity {

public boolean correct = false;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
package com.library.number;

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

import com.library.number.SimpleInt;

public class Simple extends Simplicity{
// To Specimin, both "return myInteger" and "return correct" will be seen as unsolved NameExpr instances. This test make sure that Specimin can create appropriate synthetic files for these cases.
private SimpleInt myInteger = null;
public SimpleInt getMyInteger() {
return myInteger;
}
public boolean getCorrect() {
return correct;
}
}