Skip to content

Commit

Permalink
Merge pull request #45 from tahiat/issue_38
Browse files Browse the repository at this point in the history
Crash fix for UnionType parameter in Issue #38
  • Loading branch information
kelloggm authored Nov 29, 2023
2 parents 54b1c2d + 34a10d5 commit 8b48d60
Show file tree
Hide file tree
Showing 20 changed files with 350 additions and 32 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,11 @@
import com.github.javaparser.ast.expr.NameExpr;
import com.github.javaparser.ast.expr.ObjectCreationExpr;
import com.github.javaparser.ast.expr.SuperExpr;
import com.github.javaparser.ast.stmt.CatchClause;
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.type.UnionType;
import com.github.javaparser.ast.visitor.ModifierVisitor;
import com.github.javaparser.ast.visitor.Visitable;
import com.github.javaparser.resolution.UnsolvedSymbolException;
Expand Down Expand Up @@ -220,19 +223,34 @@ public Visitable visit(MethodDeclaration method, Void p) {
@Override
public Visitable visit(Parameter para, Void p) {
if (insideTargetMethod) {
ResolvedType paraType = para.resolve().getType();
if (paraType.isReferenceType()) {
String paraTypeFullName =
paraType.asReferenceType().getTypeDeclaration().get().getQualifiedName();
usedClass.add(paraTypeFullName);
for (ResolvedType typeParameterValue : paraType.asReferenceType().typeParametersValues()) {
String typeParameterValueName = typeParameterValue.describe();
if (typeParameterValueName.contains("<")) {
// removing the "<...>" part if there is any.
typeParameterValueName =
typeParameterValueName.substring(0, typeParameterValueName.indexOf("<"));
Type type = para.getType();
if (type.isUnionType()) {
resolveUnionType(type.asUnionType());
} else {
// Parameter resolution (para.resolve()) does not work in catch clause.
// However, resolution works on the type of the parameter.
// Bug report: https://github.com/javaparser/javaparser/issues/4240
ResolvedType paramType;
if (para.getParentNode().isPresent() && para.getParentNode().get() instanceof CatchClause) {
paramType = para.getType().resolve();
} else {
paramType = para.resolve().getType();
}

if (paramType.isReferenceType()) {
String paraTypeFullName =
paramType.asReferenceType().getTypeDeclaration().get().getQualifiedName();
usedClass.add(paraTypeFullName);
for (ResolvedType typeParameterValue :
paramType.asReferenceType().typeParametersValues()) {
String typeParameterValueName = typeParameterValue.describe();
if (typeParameterValueName.contains("<")) {
// removing the "<...>" part if there is any.
typeParameterValueName =
typeParameterValueName.substring(0, typeParameterValueName.indexOf("<"));
}
usedClass.add(typeParameterValueName);
}
usedClass.add(typeParameterValueName);
}
}
}
Expand Down Expand Up @@ -310,4 +328,18 @@ public Visitable visit(NameExpr expr, Void p) {
}
return super.visit(expr, p);
}

/**
* Resolves unionType parameters one by one and adds them in the usedClass set.
*
* @param type unionType parameter
*/
private void resolveUnionType(UnionType type) {
for (ReferenceType param : type.getElements()) {
ResolvedType paramType = param.resolve();
String paraTypeFullName =
paramType.asReferenceType().getTypeDeclaration().get().getQualifiedName();
usedClass.add(paraTypeFullName);
}
}
}
28 changes: 26 additions & 2 deletions src/main/java/org/checkerframework/specimin/UnsolvedClass.java
Original file line number Diff line number Diff line change
Expand Up @@ -37,17 +37,33 @@ public class UnsolvedClass {
/** This field records the number of type variables for this class */
private int numberOfTypeVariables = 0;

/** This field records if the class is a custom exception */
private boolean isExceptionType = false;

/**
* Create an instance of UnsolvedClass
*
* @param className the name of the class
* @param packageName the name of the package
*/
public UnsolvedClass(@ClassGetSimpleName String className, String packageName) {
this(className, packageName, false);
}

/**
* Create an instance of UnsolvedClass
*
* @param className the name of the class
* @param packageName the name of the package
* @param isException does the class represents an exception?
*/
public UnsolvedClass(
@ClassGetSimpleName String className, String packageName, boolean isException) {
this.className = className;
this.methods = new LinkedHashSet<>();
this.packageName = packageName;
this.classFields = new LinkedHashSet<>();
this.isExceptionType = isException;
}

/**
Expand Down Expand Up @@ -105,7 +121,11 @@ public void addFields(String variableExpression) {
this.classFields.add(variableExpression);
}

/** This method sets the number of type variables for the current class */
/**
* This method sets the number of type variables for the current class
*
* @param numberOfTypeVariables number of type variable in this class.
*/
public void setNumberOfTypeVariables(int numberOfTypeVariables) {
this.numberOfTypeVariables = numberOfTypeVariables;
}
Expand Down Expand Up @@ -172,7 +192,11 @@ public void updateFieldByType(String currentType, String correctType) {
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append("package ").append(packageName).append(";\n");
sb.append("public class ").append(className).append(getTypeVariablesAsString()).append(" {\n");
sb.append("public class ").append(className).append(getTypeVariablesAsString());
if (isExceptionType) {
sb.append(" extends Exception");
}
sb.append(" {\n");
for (String variableDeclarations : classFields) {
sb.append(" " + "public " + variableDeclarations + ";\n");
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
import com.github.javaparser.ast.type.ReferenceType;
import com.github.javaparser.ast.type.Type;
import com.github.javaparser.ast.type.TypeParameter;
import com.github.javaparser.ast.type.UnionType;
import com.github.javaparser.ast.visitor.ModifierVisitor;
import com.github.javaparser.ast.visitor.Visitable;
import com.github.javaparser.resolution.UnsolvedSymbolException;
Expand All @@ -57,6 +58,7 @@
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import org.checkerframework.checker.nullness.qual.NonNull;
import org.checkerframework.checker.nullness.qual.Nullable;
import org.checkerframework.checker.signature.qual.ClassGetSimpleName;
import org.checkerframework.checker.signature.qual.DotSeparatedIdentifiers;
Expand Down Expand Up @@ -526,7 +528,7 @@ public Visitable visit(MethodDeclaration node, Void arg) {
try {
nodeType.resolve();
} catch (UnsolvedSymbolException | UnsupportedOperationException e) {
updateUnsolvedClassWithClassName(nodeTypeSimpleForm);
updateUnsolvedClassWithClassName(nodeTypeSimpleForm, false);
}
}

Expand Down Expand Up @@ -655,24 +657,22 @@ public Visitable visit(ClassOrInterfaceType typeExpr, Void p) {
@Override
public Visitable visit(Parameter parameter, Void p) {
try {
parameter.resolve();
if (parameter.getType() instanceof UnionType) {
resolveUnionType(parameter);
} else {
if (parameter.getParentNode().isPresent()
&& parameter.getParentNode().get() instanceof CatchClause) {
parameter.getType().resolve();
} else {
parameter.resolve();
}
}
return super.visit(parameter, p);
}
// If the parameter originates from a Java built-in library, such as java.io or java.lang,
// an UnsupportedOperationException will be thrown instead.
catch (UnsolvedSymbolException | UnsupportedOperationException 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();
updateUnsolvedClassWithClassName(className);
}
handleParameterResolveFailure(parameter);
}
gotException = true;
return super.visit(parameter, p);
Expand All @@ -695,7 +695,7 @@ public Visitable visit(ObjectCreationExpr newExpr, Void p) {
try {
List<String> argumentsCreation = getArgumentsFromObjectCreation(newExpr);
UnsolvedMethod creationMethod = new UnsolvedMethod("", type, argumentsCreation);
updateUnsolvedClassWithClassName(type, creationMethod);
updateUnsolvedClassWithClassName(type, false, creationMethod);
} catch (Exception q) {
// can not solve the parameters for this object creation in this current run
}
Expand All @@ -705,6 +705,46 @@ public Visitable visit(ObjectCreationExpr newExpr, Void p) {
return newExpr;
}

/**
* @param parameter parameter from visitor method which is unsolvable.
*/
private void handleParameterResolveFailure(@NonNull Parameter parameter) {
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();
if (parameter.getParentNode().isPresent()
&& parameter.getParentNode().get() instanceof CatchClause) {
updateUnsolvedClassWithClassName(className, true);
} else {
updateUnsolvedClassWithClassName(className, false);
}
}
}

/**
* Given the unionType parameter, this method will try resolving each element separately. If any
* of the element is unsolvable, an unsolved class instance will be created to generate synthetic
* class for the element.
*
* @param parameter unionType parameter from visitor class
*/
private void resolveUnionType(@NonNull Parameter parameter) {
for (var param : parameter.getType().asUnionType().getElements()) {
try {
param.resolve();
} catch (UnsolvedSymbolException | UnsupportedOperationException e) {
handleParameterResolveFailure(parameter);
}
}
}

/**
* 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
Expand Down Expand Up @@ -790,7 +830,7 @@ public void updateUnsolvedClassWithMethod(
returnType = desiredReturnType;
}
UnsolvedMethod thisMethod = new UnsolvedMethod(methodName, returnType, listOfParameters);
UnsolvedClass missingClass = updateUnsolvedClassWithClassName(className, thisMethod);
UnsolvedClass missingClass = updateUnsolvedClassWithClassName(className, false, thisMethod);
syntheticMethodAndClass.put(methodName, missingClass);

// if the return type is not specified, a synthetic return type will be created. This part of
Expand Down Expand Up @@ -873,15 +913,18 @@ public void updateClassesFromJarSourcesForMethodCall(MethodCallExpr expr) {
* @param nameOfClass the name of an unsolved class
* @param unsolvedMethods unsolved methods to add to the class before updating this visitor's set
* missing classes (optional, may be omitted)
* @param isExceptionType if the class is of exceptionType
* @return the newly-created UnsolvedClass method, for further processing. This output may be
* ignored.
*/
public UnsolvedClass updateUnsolvedClassWithClassName(
@ClassGetSimpleName String nameOfClass, UnsolvedMethod... unsolvedMethods) {
@ClassGetSimpleName String nameOfClass,
boolean isExceptionType,
UnsolvedMethod... unsolvedMethods) {
// if the name of the class is not present among import statements, we assume that this unsolved
// class is in the same directory as the current class
String packageName = classAndPackageMap.getOrDefault(nameOfClass, currentPackage);
UnsolvedClass result = new UnsolvedClass(nameOfClass, packageName);
UnsolvedClass result = new UnsolvedClass(nameOfClass, packageName, isExceptionType);
for (UnsolvedMethod unsolvedMethod : unsolvedMethods) {
result.addMethod(unsolvedMethod);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package org.checkerframework.specimin;

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

public class CustomExceptionTest {
@Test
public void runTest() throws IOException {
SpeciminTestExecutor.runTestWithoutJarPaths(
"customexception",
new String[] {"com/example/Simple.java"},
new String[] {"com.example.Simple#test()"});
}
}
15 changes: 15 additions & 0 deletions src/test/java/org/checkerframework/specimin/Issue38Test.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package org.checkerframework.specimin;

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

/** This test checks if Specimin will work for Union types */
public class Issue38Test {
@Test
public void runTest() throws IOException {
SpeciminTestExecutor.runTestWithoutJarPaths(
"issue38",
new String[] {"com/example/Issue38.java"},
new String[] {"com.example.Issue38#test()"});
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package org.checkerframework.specimin;

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

public class NestedCatchClauseTest {
@Test
public void runTest() throws IOException {
SpeciminTestExecutor.runTestWithoutJarPaths(
"nestedcatchclause",
new String[] {"com/example/Simple.java"},
new String[] {"com.example.Simple#test()"});
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package org.checkerframework.specimin;

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

public class UnsolvableExceptionTest {
@Test
public void runTest() throws IOException {
SpeciminTestExecutor.runTestWithoutJarPaths(
"unsolvedexception",
new String[] {"com/example/Simple.java"},
new String[] {"com.example.Simple#test()"});
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package com.example;

public class CustomException extends Exception {

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

public class Simple {

public void test() {
try {
throw new CustomException("dummy");
} catch (CustomException e) {
e.printStackTrace();
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package com.example;
public class CustomException extends Exception {
public CustomException (String msg) {
}
}
14 changes: 14 additions & 0 deletions src/test/resources/customexception/input/com/example/Simple.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package com.example;

import org.checkerframework.specimin.CustomExceptionTest;

public class Simple {

public void test() {
try {
throw new CustomException("dummy");
} catch (CustomException e) {
e.printStackTrace();
}
}
}
Loading

0 comments on commit 8b48d60

Please sign in to comment.