Skip to content

Commit

Permalink
Merge pull request #22 from LoiNguyenCS/extension-class
Browse files Browse the repository at this point in the history
Add codes for extension classes
  • Loading branch information
kelloggm authored Jul 27, 2023
2 parents 74d8fde + b467e6d commit 4f03e50
Show file tree
Hide file tree
Showing 36 changed files with 823 additions and 108 deletions.
13 changes: 13 additions & 0 deletions JavaParser.astub
Original file line number Diff line number Diff line change
Expand Up @@ -16,4 +16,17 @@ package com.github.javaparser.ast.expr;

class SimpleName {
@ClassGetSimpleName String asString();
}

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();
}
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 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.

Expand Down
20 changes: 16 additions & 4 deletions src/main/java/org/checkerframework/specimin/SpeciminRunner.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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<String> rootOption = optionParser.accepts("root").withRequiredArg();

OptionSpec<String> 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<String> targetFilesOption = optionParser.accepts("targetFile").withRequiredArg();
Expand All @@ -57,11 +60,15 @@ public static void main(String... args) throws IOException {

String root = options.valueOf(rootOption);
List<String> targetFiles = options.valuesOf(targetFilesOption);
List<String> 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);

Expand All @@ -72,6 +79,7 @@ public static void main(String... args) throws IOException {
}

UnsolvedSymbolVisitor addMissingClass = new UnsolvedSymbolVisitor(root);
addMissingClass.setClassesFromJar(jarPaths);
/**
* The set of path of files that have been created by addMissingClass. We will delete all those
* files in the end.
Expand All @@ -97,7 +105,6 @@ public static void main(String... args) throws IOException {
}
}
List<String> targetMethodNames = options.valuesOf(targetMethodsOption);

// 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.
Expand Down Expand Up @@ -127,7 +134,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();
Expand Down Expand Up @@ -156,7 +162,13 @@ public static void main(String... args) throws IOException {
for (Entry<String, CompilationUnit> target : parsedTargetFiles.entrySet()) {
// If a compilation output's entire body has been removed, do not output it.
if (isEmptyCompilationUnit(target.getValue())) {
continue;
boolean isASyntheticReturnType = addMissingClass.isASyntheticReturnType(target.getKey());
boolean isASyntheticSuperClass =
!addMissingClass.getSuperClass().equals("")
&& target.getKey().contains(addMissingClass.getSuperClass());
if (!isASyntheticSuperClass && !isASyntheticReturnType) {
continue;
}
}

Path targetOutputPath = Path.of(outputDirectory, target.getKey());
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,14 @@
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.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.ArrayList;
Expand Down Expand Up @@ -134,6 +139,21 @@ public Visitable visit(ClassOrInterfaceDeclaration decl, Void p) {
return result;
}

@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 + "#" + constructorMethodAsString;
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);
Expand Down Expand Up @@ -168,4 +188,22 @@ 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);
}

@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);
}
}
27 changes: 27 additions & 0 deletions src/main/java/org/checkerframework/specimin/UnsolvedClass.java
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,9 @@ public class UnsolvedClass {
/** The name of the class */
private final @ClassGetSimpleName String className;

/** The fields of this class */
private final Set<String> classFields;

/**
* The name of the package of the class. We rely on the import statements from the source codes to
* guess the package name.
Expand All @@ -31,6 +34,7 @@ public UnsolvedClass(@ClassGetSimpleName String className, String packageName) {
this.className = className;
this.methods = new HashSet<>();
this.packageName = packageName;
this.classFields = new HashSet<>();
}

/**
Expand Down Expand Up @@ -59,6 +63,16 @@ public Set<UnsolvedMethod> getMethods() {
public String getPackageName() {
return packageName;
}

/**
* Get the fields of this current class
*
* @return classVariables
*/
public Set<String> getClassFields() {
return classFields;
}

/**
* Add a method to the class
*
Expand All @@ -68,6 +82,16 @@ public void addMethod(UnsolvedMethod method) {
this.methods.add(method);
}

/**
* 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 addFields(String variableExpression) {
this.classFields.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.
Expand All @@ -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 : classFields) {
sb.append(" " + variableDeclarations + ";\n");
}
for (UnsolvedMethod method : methods) {
sb.append(method.toString());
}
Expand Down
Loading

0 comments on commit 4f03e50

Please sign in to comment.