Skip to content

Commit

Permalink
add codes and tests for unsolved field access
Browse files Browse the repository at this point in the history
  • Loading branch information
LoiNguyenCS committed Dec 1, 2023
1 parent 85741c1 commit 90a4b52
Show file tree
Hide file tree
Showing 24 changed files with 348 additions and 7 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -306,6 +306,7 @@ public Visitable visit(FieldAccessExpr expr, Void p) {
fullNameOfClass = expr.resolve().asField().declaringType().getQualifiedName();
usedMembers.add(fullNameOfClass + "#" + expr.getName().asString());
usedClass.add(fullNameOfClass);
usedClass.add(expr.resolve().getType().describe());
} catch (UnsolvedSymbolException e) {
// if the a field is accessed in the form of a fully-qualified path, such as
// org.example.A.b, then other components in the path apart from the class name and field
Expand Down
20 changes: 16 additions & 4 deletions src/main/java/org/checkerframework/specimin/UnsolvedClass.java
Original file line number Diff line number Diff line change
Expand Up @@ -164,17 +164,29 @@ public void updateFieldByType(String currentType, String correctType) {
Iterator<String> iterator = classFields.iterator();
Set<String> newFields = new HashSet<>();
while (iterator.hasNext()) {
List<String> elements = Splitter.on(' ').splitToList(iterator.next());
String fieldDeclared = iterator.next();
boolean isStatic = false;
if (fieldDeclared.startsWith("static")) {
fieldDeclared = fieldDeclared.replace("static ", "");
isStatic = true;
}
List<String> elements = Splitter.on(' ').splitToList(fieldDeclared);
// fieldExpression is guaranteed to have the form "TYPE FIELD_NAME". Since this field
// expression is from a synthetic class, there is no annotation involved, so TYPE has no
// space.
String fieldType = elements.get(0);
String fieldName = elements.get(1);
if (fieldType.equals(currentType)) {
iterator.remove();
newFields.add(
UnsolvedSymbolVisitor.setInitialValueForVariableDeclaration(
correctType, correctType + " " + fieldName));
if (!isStatic) {
newFields.add(
UnsolvedSymbolVisitor.setInitialValueForVariableDeclaration(
correctType, correctType + " " + fieldName));
} else {
newFields.add(
UnsolvedSymbolVisitor.setInitialValueForVariableDeclaration(
correctType, "static " + correctType + " " + fieldName));
}
}
}

Expand Down
145 changes: 142 additions & 3 deletions src/main/java/org/checkerframework/specimin/UnsolvedSymbolVisitor.java
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,12 @@ public class UnsolvedSymbolVisitor extends ModifierVisitor<Void> {
/** This instance maps the name of a synthetic method with its synthetic class */
private final Map<String, UnsolvedClass> syntheticMethodAndClass = new HashMap<>();

/**
* This instance maps the name of a synthetic type with the class where there is a field declared
* with that type
*/
private final Map<String, UnsolvedClass> syntheticTypeAndClass = new HashMap<>();

/**
* This is to check if the current synthetic files are enough to prevent UnsolvedSymbolException
* or we still need more.
Expand Down Expand Up @@ -561,6 +567,30 @@ public Visitable visit(MethodDeclaration node, Void arg) {
public Visitable visit(FieldAccessExpr node, Void p) {
if (isASuperCall(node) && !canBeSolved(node)) {
updateSyntheticClassForSuperCall(node);
} else if (canBeSolved(node)) {
return super.visit(node, p);
} else if (isAQualifiedFieldSignature(node.toString())) {
updateClassSetWithQualifiedFieldSignature(node.toString(), true);
} else if (unsolvedFieldCalledByASimpleClassName(node)) {
String simpleClassName = node.getScope().toString();
String fullyQualifiedCall =
classAndPackageMap.getOrDefault(simpleClassName, currentPackage) + "." + node;
updateClassSetWithQualifiedFieldSignature(fullyQualifiedCall, true);
}
// if the symbol that called this field is solvable yet this field is unsolved, then the type of
// the calling symbol is a synthetic class that needs to have a synthetic field updated
else if (canBeSolved(node.getScope())) {
updateSyntheticClassWithNonStaticFields(node);
}

try {
node.resolve();
} catch (UnsolvedSymbolException e) {
// for a qualified name field access such as org.sample.MyClass.field, org.sample will also be
// considered FieldAccessExpr.
if (isAClassPath(node.getScope().toString())) {
this.gotException = true;
}
}
return super.visit(node, p);
}
Expand Down Expand Up @@ -1001,6 +1031,19 @@ public HashSet<String> getParameterFromAMethodDeclaration(MethodDeclaration decl
return setOfParameters;
}

/**
* Given a non-static and unsolved field access expression, this method will update the
* corresponding synthetic class.
*
* @param field a non-static field access expression
*/
public void updateSyntheticClassWithNonStaticFields(FieldAccessExpr field) {
Expression caller = field.getScope();
String fullyQualifiedClassName = caller.calculateResolvedType().describe();
String fieldQualifedSignature = fullyQualifiedClassName + "." + field.getNameAsString();
updateClassSetWithQualifiedFieldSignature(fieldQualifedSignature, false);
}

/**
* For a super call, this method will update the corresponding synthetic class
*
Expand All @@ -1018,13 +1061,13 @@ public void updateSyntheticClassForSuperCall(Expression expr) {
methodAndReturnType.getOrDefault(expr.asMethodCallExpr().getNameAsString(), ""));
} else if (expr instanceof FieldAccessExpr) {
String nameAsString = expr.asFieldAccessExpr().getNameAsString();
updateUnsolvedClassWithFields(
updateUnsolvedSuperClassWithFields(
nameAsString,
getParentClass(className),
classAndPackageMap.getOrDefault(getParentClass(className), this.currentPackage));
} else if (expr instanceof NameExpr) {
String nameAsString = expr.asNameExpr().getNameAsString();
updateUnsolvedClassWithFields(
updateUnsolvedSuperClassWithFields(
nameAsString,
getParentClass(className),
classAndPackageMap.getOrDefault(getParentClass(className), this.currentPackage));
Expand All @@ -1044,7 +1087,7 @@ public void updateSyntheticClassForSuperCall(Expression expr) {
* @param className the name of the synthetic class
* @param packageName the package of the synthetic class
*/
public void updateUnsolvedClassWithFields(
public void updateUnsolvedSuperClassWithFields(
String var, @ClassGetSimpleName String className, String packageName) {
UnsolvedClass relatedClass = new UnsolvedClass(className, packageName);
if (variablesAndDeclaration.containsKey(var)) {
Expand Down Expand Up @@ -1465,6 +1508,24 @@ public boolean unsolvedAndCalledByASimpleClassName(MethodCallExpr method) {
}
}

/**
* Check whether a field, invoked by a simple class name, is unsolved
*
* @param field the field to be checked
* @return true if the field is unsolved and invoked by a simple class name
*/
public boolean unsolvedFieldCalledByASimpleClassName(FieldAccessExpr field) {
try {
field.resolve();
return false;
} catch (UnsolvedSymbolException e) {
// this check is not very comprehensive, since a class can be in lowercase, and a method or
// field can be in uppercase. But since this is without the jar paths, this is the best we can
// do.
return Character.isUpperCase(field.getScope().toString().charAt(0));
}
}

/**
* Returns the fully-qualified class name version of a method call invoked by a simple class name.
*
Expand Down Expand Up @@ -1506,6 +1567,20 @@ public boolean isAnUnsolvedStaticMethodCalledByAQualifiedClassName(MethodCallExp
}
}

/**
* This method checks if a field access expression is a qualified field signature. For example,
* for this field access expression: org.package.Class.firstField.secondField, this method will
* return true for "org.package.Class.firstField", but not for
* "org.package.Class.firstField.secondField".
*
* @param field the field access expression to be checked
* @return true if field is a qualified field signature
*/
public boolean isAQualifiedFieldSignature(String field) {
String caller = field.substring(0, field.lastIndexOf("."));
return isAClassPath(caller);
}

/**
* Creates a synthetic class corresponding to a static method called by a qualified class name.
* Ensure to check with {@link #isAnUnsolvedStaticMethodCalledByAQualifiedClassName} before
Expand Down Expand Up @@ -1565,6 +1640,62 @@ public void updateClassSetWithQualifiedStaticMethodCall(
this.updateMissingClass(classThatContainMethod);
}

/**
* Creates a synthetic class corresponding to a static field called by a qualified class name.
* Ensure to check with {@link #isAQualifiedFieldSignature(String)} before calling this method.
*
* @param fieldExpr the field access expression to be used as input. This field access expression
* must be in the form of a qualified class name
* @param isStatic check whether the field is static
*/
public void updateClassSetWithQualifiedFieldSignature(String fieldExpr, boolean isStatic) {
// As this code involves complex string operations, we'll use a field access expression as an
// example,
// following its progression through the code.
// Suppose this is our field access expression: com.example.MyClass.myField
List<String> fieldParts = Splitter.onPattern("[.]").splitToList(fieldExpr);
int numOfFieldParts = fieldParts.size();
if (numOfFieldParts <= 2) {
throw new RuntimeException(
"Need to check this field access expression with"
+ " isAnUnsolvedStaticFieldCalledByAQualifiedClassName before using this method");
}
String fieldTypeClassName = toCapital(fieldParts.get(0));
String packageName = fieldParts.get(0);
// According to the above example, fieldName will be myField
String fieldName = fieldParts.get(numOfFieldParts - 1);
@SuppressWarnings(
"signature") // this className is from the second-to-last part of a fully-qualified method
// call, which is the simple name of a class. In this case, it is MyClass.
@ClassGetSimpleName String className = fieldParts.get(numOfFieldParts - 2);
// After this loop: fieldTypeClassName will be ComExample, and packageName will be com.example
for (int i = 1; i < numOfFieldParts - 2; i++) {
fieldTypeClassName = fieldTypeClassName + toCapital(fieldParts.get(i));
packageName = packageName + "." + fieldParts.get(i);
}
// At this point, fieldTypeClassName will be ComExampleMyClassMyFieldType
fieldTypeClassName = fieldTypeClassName + toCapital(className) + toCapital(fieldName) + "Type";
// since fieldTypeClassName is just a single long string without any dot in the middle, it will
// be a simple name.
@SuppressWarnings("signature")
@ClassGetSimpleName String thisFieldType = fieldTypeClassName;
UnsolvedClass typeClass = new UnsolvedClass(thisFieldType, packageName);
UnsolvedClass classThatContainField = new UnsolvedClass(className, packageName);
// at this point, fieldDeclaration will become "ComExampleMyClassMyFieldType myField"
String fieldDeclaration = fieldTypeClassName + " " + fieldName;
if (isStatic) {
fieldDeclaration = "static " + fieldDeclaration;
}
// fieldDeclaration will become "static ComExampleMyClassMyFieldType myField = null;"
fieldDeclaration = setInitialValueForVariableDeclaration(fieldTypeClassName, fieldDeclaration);
classThatContainField.addFields(fieldDeclaration);
classAndPackageMap.put(thisFieldType, packageName);
classAndPackageMap.put(className, packageName);
syntheticTypeAndClass.put(thisFieldType, classThatContainField);
this.updateMissingClass(typeClass);
this.updateMissingClass(classThatContainField);
}

/**
* Based on the Map returned by JavaTypeCorrect, this method updates the types of methods in
* synthetic classes.
Expand All @@ -1573,6 +1704,14 @@ public void updateClassSetWithQualifiedStaticMethodCall(
*/
public void updateTypes(Map<String, String> typeToCorrect) {
for (String incorrectType : typeToCorrect.keySet()) {
// update incorrecType if it is the type of a field in a synthetic class
if (syntheticTypeAndClass.containsKey(incorrectType)) {
UnsolvedClass relatedClass = syntheticTypeAndClass.get(incorrectType);
relatedClass.updateFieldByType(incorrectType, typeToCorrect.get(incorrectType));
this.deleteOldSyntheticClass(relatedClass);
this.createMissingClass(relatedClass);
return;
}
// convert MethodNameReturnType to methodName
String involvedMethod =
incorrectType.substring(0, 1).toLowerCase()
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package org.checkerframework.specimin;

import org.junit.Test;

import java.io.IOException;

/**
* This test checks that if Specimin will work if there is an unsolved, static field in a qualified name form used by a target method.
*/
public class UnsolvedStaticQualifiedField {
@Test
public void runTest() throws IOException {
SpeciminTestExecutor.runTestWithoutJarPaths(
"unsolvedstaticqualifiedfield",
new String[] {"com/example/Foo.java"},
new String[] {"com.example.Foo#bar()"});
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package org.checkerframework.specimin;

import org.junit.Test;

import java.io.IOException;

/**
* This test checks that if Specimin will work if there is an unsolved, static field in a simple name form used by target methods
*/
public class UnsolvedStaticSimpleField {
@Test
public void runTest() throws IOException {
SpeciminTestExecutor.runTestWithoutJarPaths(
"unsolvedstaticsimplefield",
new String[] {"com/example/Foo.java"},
new String[] {"com.example.Foo#bar()"});
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package org.checkerframework.specimin;

import org.junit.Test;

import java.io.IOException;

/**
* This test checks that if Specimin will work if there is an unsolved, static field in a simple name form with a primitive type.
*/
public class UnsolvedStaticSimplePrimitiveField {
@Test
public void runTest() throws IOException {
SpeciminTestExecutor.runTestWithoutJarPaths(
"unsolvedstaticsimpleprimitivefield",
new String[] {"com/example/Foo.java"},
new String[] {"com.example.Foo#bar()"});
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package com.example;

import org.sampling.Baz;

class Foo {

void test() {
Baz testing = new Baz();
testing.cal.doAddition();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package org.sampling;

public class Baz {

public OrgSamplingBazCalType cal;

public Baz() {
throw new Error();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
package org.sampling;

public class DoAdditionReturnType {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package org.sampling;

public class OrgSamplingBazCalType {

public DoAdditionReturnType doAddition() {
throw new Error();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package com.example;
import org.sampling.Baz;
class Foo {
void test() {
Baz testing = new Baz();
testing.cal.doAddition();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package com.example;

class Foo {

void bar() {
org.sampling.Baz.myField.doAddition();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package org.sampling;

public class Baz {

public static OrgSamplingBazMyFieldType myField;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
package org.sampling;

public class DoAdditionReturnType {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package org.sampling;

public class OrgSamplingBazMyFieldType {

public DoAdditionReturnType doAddition() {
throw new Error();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package com.example;

class Foo {
void bar() {
org.sampling.Baz.myField.doAddition();
}
}

Loading

0 comments on commit 90a4b52

Please sign in to comment.