-
Notifications
You must be signed in to change notification settings - Fork 7
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #58 from LoiNguyenCS/minimize-fields
Minimize fields
- Loading branch information
Showing
18 changed files
with
259 additions
and
125 deletions.
There are no files selected for viewing
109 changes: 0 additions & 109 deletions
109
src/main/java/org/checkerframework/specimin/MethodPrunerVisitor.java
This file was deleted.
Oops, something went wrong.
184 changes: 184 additions & 0 deletions
184
src/main/java/org/checkerframework/specimin/PrunerVisitor.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,184 @@ | ||
package org.checkerframework.specimin; | ||
|
||
import com.github.javaparser.StaticJavaParser; | ||
import com.github.javaparser.ast.ImportDeclaration; | ||
import com.github.javaparser.ast.Node; | ||
import com.github.javaparser.ast.body.ConstructorDeclaration; | ||
import com.github.javaparser.ast.body.FieldDeclaration; | ||
import com.github.javaparser.ast.body.MethodDeclaration; | ||
import com.github.javaparser.ast.body.VariableDeclarator; | ||
import com.github.javaparser.ast.expr.BooleanLiteralExpr; | ||
import com.github.javaparser.ast.expr.CharLiteralExpr; | ||
import com.github.javaparser.ast.expr.DoubleLiteralExpr; | ||
import com.github.javaparser.ast.expr.Expression; | ||
import com.github.javaparser.ast.expr.IntegerLiteralExpr; | ||
import com.github.javaparser.ast.expr.LongLiteralExpr; | ||
import com.github.javaparser.ast.expr.NullLiteralExpr; | ||
import com.github.javaparser.ast.type.PrimitiveType; | ||
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.ResolvedConstructorDeclaration; | ||
import com.github.javaparser.resolution.declarations.ResolvedMethodDeclaration; | ||
import java.util.Iterator; | ||
import java.util.Set; | ||
|
||
/** | ||
* This visitor removes every member in the compilation unit that is not a member of its {@link | ||
* #methodsToLeaveUnchanged} set or {@link #membersToEmpty} set. It also deletes the bodies of all | ||
* methods and replaces them with "throw new Error();" or remove the initializers of fields | ||
* (minimized if the field is final) within the {@link #membersToEmpty} set. | ||
*/ | ||
public class PrunerVisitor extends ModifierVisitor<Void> { | ||
|
||
/** | ||
* The methods that should NOT be touched by this pruner. The strings representing the method are | ||
* those returned by ResolvedMethodDeclaration#getQualifiedSignature. | ||
*/ | ||
private Set<String> methodsToLeaveUnchanged; | ||
|
||
/** | ||
* The members, fields and methods, to be pruned. For methods, the bodies are removed. For fields, | ||
* the initializers are removed, or minimized in case the field is final. The strings representing | ||
* the method are those returned by ResolvedMethodDeclaration#getQualifiedSignature. The strings | ||
* representing the field are returned by ResolvedTypeDeclaration#getQualifiedName. | ||
*/ | ||
private Set<String> membersToEmpty; | ||
|
||
/** | ||
* This is the set of classes used by the target methods. We use this set to determine if we | ||
* should keep or delete an import statement. The strings representing the classes are in | ||
* the @FullyQualifiedName form. | ||
*/ | ||
private Set<String> classesUsedByTargetMethods; | ||
|
||
/** | ||
* This boolean tracks whether the element currently being visited is inside a target method. It | ||
* is set by {@link #visit(MethodDeclaration, Void)}. | ||
*/ | ||
private boolean insideTargetMethod = false; | ||
|
||
/** | ||
* Creates the pruner. All members this pruner encounters other than those in its input sets will | ||
* be removed entirely. For methods in both arguments, the Strings should be in the format | ||
* produced by ResolvedMethodDeclaration#getQualifiedSignature. For fields in {@link | ||
* #membersToEmpty}, the Strings should be in the format produced by | ||
* ResolvedTypeDeclaration#getQualifiedName. | ||
* | ||
* @param methodsToKeep the set of methods whose bodies should be kept intact (usually the target | ||
* methods for specimin) | ||
* @param membersToEmpty the set of members that this pruner will empty | ||
* @param classesUsedByTargetMethods the classes used by target methods | ||
*/ | ||
public PrunerVisitor( | ||
Set<String> methodsToKeep, | ||
Set<String> membersToEmpty, | ||
Set<String> classesUsedByTargetMethods) { | ||
this.methodsToLeaveUnchanged = methodsToKeep; | ||
this.membersToEmpty = membersToEmpty; | ||
this.classesUsedByTargetMethods = classesUsedByTargetMethods; | ||
} | ||
|
||
@Override | ||
public Node visit(ImportDeclaration decl, Void p) { | ||
String classFullName = decl.getNameAsString(); | ||
if (classesUsedByTargetMethods.contains(classFullName)) { | ||
return super.visit(decl, p); | ||
} | ||
decl.remove(); | ||
return decl; | ||
} | ||
|
||
@Override | ||
public Visitable visit(MethodDeclaration methodDecl, Void p) { | ||
ResolvedMethodDeclaration resolved = methodDecl.resolve(); | ||
if (methodsToLeaveUnchanged.contains(resolved.getQualifiedSignature())) { | ||
insideTargetMethod = true; | ||
Visitable result = super.visit(methodDecl, p); | ||
insideTargetMethod = false; | ||
return result; | ||
} else if (membersToEmpty.contains(resolved.getQualifiedSignature())) { | ||
methodDecl.setBody(StaticJavaParser.parseBlock("{ throw new Error(); }")); | ||
return methodDecl; | ||
} else { | ||
// if insideTargetMethod is true, this current method declaration belongs to an anonnymous | ||
// class inside the target method. | ||
if (!insideTargetMethod) { | ||
methodDecl.remove(); | ||
} | ||
return methodDecl; | ||
} | ||
} | ||
|
||
@Override | ||
public Visitable visit(ConstructorDeclaration constructorDecl, Void p) { | ||
ResolvedConstructorDeclaration resolved = constructorDecl.resolve(); | ||
if (methodsToLeaveUnchanged.contains(resolved.getQualifiedSignature())) { | ||
return super.visit(constructorDecl, p); | ||
} else if (membersToEmpty.contains(resolved.getQualifiedSignature())) { | ||
constructorDecl.setBody(StaticJavaParser.parseBlock("{ throw new Error(); }")); | ||
return constructorDecl; | ||
} else { | ||
constructorDecl.remove(); | ||
return constructorDecl; | ||
} | ||
} | ||
|
||
@Override | ||
public Visitable visit(FieldDeclaration fieldDecl, Void p) { | ||
String classFullName = fieldDecl.resolve().declaringType().getQualifiedName(); | ||
boolean isFinal = fieldDecl.isFinal(); | ||
Iterator<VariableDeclarator> iterator = fieldDecl.getVariables().iterator(); | ||
while (iterator.hasNext()) { | ||
VariableDeclarator varDecl = iterator.next(); | ||
String varFullName = classFullName + "#" + varDecl.getNameAsString(); | ||
|
||
if (membersToEmpty.contains(varFullName)) { | ||
varDecl.removeInitializer(); | ||
if (isFinal) { | ||
varDecl.setInitializer(getBasicInitializer(varDecl.getType())); | ||
} | ||
} else { | ||
iterator.remove(); | ||
} | ||
} | ||
|
||
return super.visit(fieldDecl, p); | ||
} | ||
|
||
/** | ||
* Creates a basic initializer expression for a specified field type. 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 fieldType The type for which to generate the basic initializer. | ||
* @return An Expression representing the basic initializer for the given field type. | ||
*/ | ||
private Expression getBasicInitializer(Type fieldType) { | ||
if (fieldType.isPrimitiveType()) { | ||
PrimitiveType.Primitive primitiveType = ((PrimitiveType) fieldType).getType(); | ||
switch (primitiveType) { | ||
case BOOLEAN: | ||
return new BooleanLiteralExpr(false); | ||
case INT: | ||
return new IntegerLiteralExpr("0"); | ||
case LONG: | ||
return new LongLiteralExpr("0L"); | ||
case FLOAT: | ||
return new DoubleLiteralExpr("0.0f"); | ||
case DOUBLE: | ||
return new DoubleLiteralExpr("0.0"); | ||
case BYTE: | ||
return new IntegerLiteralExpr("0"); | ||
case SHORT: | ||
return new IntegerLiteralExpr("0"); | ||
case CHAR: | ||
return new CharLiteralExpr("'\u0000'"); | ||
default: | ||
throw new RuntimeException("Unexpected primitive type: " + fieldType); | ||
} | ||
} else { | ||
return new NullLiteralExpr(); | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
15 changes: 15 additions & 0 deletions
15
src/test/java/org/checkerframework/specimin/FieldToEmptyTest.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 can remove unused fields and minimized used fields. */ | ||
public class FieldToEmptyTest { | ||
@Test | ||
public void runTest() throws IOException { | ||
SpeciminTestExecutor.runTestWithoutJarPaths( | ||
"fieldtoempty", | ||
new String[] {"com/example/Simple.java"}, | ||
new String[] {"com.example.Simple#test()"}); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -2,5 +2,5 @@ | |
|
||
public class Parent { | ||
|
||
public sample.pack.MyType theName = null; | ||
public sample.pack.MyType theName; | ||
} |
2 changes: 1 addition & 1 deletion
2
src/test/resources/crossclassvariable/expected/com/example/Baz.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,5 +1,5 @@ | ||
package com.example; | ||
|
||
public class Baz { | ||
static int x = 5; | ||
static int x; | ||
} |
13 changes: 13 additions & 0 deletions
13
src/test/resources/fieldtoempty/expected/com/example/Simple.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
package com.example; | ||
|
||
class Simple { | ||
|
||
final int b = 0; | ||
|
||
int c; | ||
|
||
void test() { | ||
c++; | ||
b++; | ||
} | ||
} |
Oops, something went wrong.