Skip to content

Commit

Permalink
Optimize field registry population mechanism by parsing each source f…
Browse files Browse the repository at this point in the history
…ile only once (#230)

In [FieldRegistryPopulation](https://github.com/ucr-riple/NullAwayAnnotator/blob/6c17b8bccb08517f5ce3249d3285405ceeb47748/annotator-core/src/main/java/edu/ucr/cs/riple/core/registries/field/FieldRegistry.java#L97) we process a single compilation unit tree multiple times as this method receives a pair of a class flat name and a path to source file containing that class, hence, for compilation unit trees that contains multiple classes, inner classes and anonymous classes we parse the same source file multiple times. This PR updates this mechanism by caching the latest parsed source file. This optimization is according to the assumption that Scanner visits all classes within a single compilation unit tree consecutively.
  • Loading branch information
nimakarimipour authored May 15, 2024
1 parent 6c17b8b commit 2229914
Showing 1 changed file with 60 additions and 43 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
import com.github.javaparser.ast.NodeList;
import com.github.javaparser.ast.body.BodyDeclaration;
import com.github.javaparser.ast.body.VariableDeclarator;
import com.github.javaparser.utils.Pair;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Multimap;
import com.google.common.collect.MultimapBuilder;
Expand Down Expand Up @@ -92,46 +93,64 @@ protected void setup() {

@Override
protected Builder<ClassFieldRecord> getBuilder() {
return values -> {
// Class flat name.
String clazz = values[0];
// Path to class.
Path path = Helper.deserializePath(values[1]);
CompilationUnit tree = Injector.parse(path);
if (tree == null) {
return null;
}
NodeList<BodyDeclaration<?>> members;
try {
members = Helper.getTypeDeclarationMembersByFlatName(tree, clazz);
} catch (TargetClassNotFound notFound) {
System.err.println(notFound.getMessage());
return null;
return new Builder<>() {
Pair<Path, CompilationUnit> lastParsedSourceFile = new Pair<>(null, null);

@Override
public ClassFieldRecord build(String[] values) {
// This method is called with values in format of: [class flat name, path to source file].
// To avoid parsing a source file multiple times, we keep the last parsed
// source file in a reference.
// This optimization is according to the assumption that Scanner
// visits all classes within a single compilation unit tree consecutively.
// Path to class.
Path path = Helper.deserializePath(values[1]);
CompilationUnit tree;
if (lastParsedSourceFile.a != null && lastParsedSourceFile.a.equals(path)) {
// Already visited.
tree = lastParsedSourceFile.b;
} else {
// Not visited yet, parse the source file.
tree = Injector.parse(path);
lastParsedSourceFile = new Pair<>(path, tree);
}
if (tree == null) {
return null;
}
NodeList<BodyDeclaration<?>> members;
// Class flat name.
String clazz = values[0];
try {
members = Helper.getTypeDeclarationMembersByFlatName(tree, clazz);
} catch (TargetClassNotFound notFound) {
System.err.println(notFound.getMessage());
return null;
}
ClassFieldRecord record = new ClassFieldRecord(path, clazz);
members.forEach(
bodyDeclaration ->
bodyDeclaration.ifFieldDeclaration(
fieldDeclaration -> {
NodeList<VariableDeclarator> vars = fieldDeclaration.getVariables();
if (vars.getFirst().isEmpty()) {
// unexpected but just in case.
return;
}
record.addFieldDeclaration(fieldDeclaration);
// Collect uninitialized fields at declaration.
vars.forEach(
variableDeclarator -> {
String fieldName = variableDeclarator.getNameAsString();
if (variableDeclarator.getInitializer().isEmpty()) {
uninitializedFields.put(clazz, fieldName);
}
});
}));
// We still want to keep the information about the class even if it has no field
// declarations, so we can retrieve tha path to the file from the given class flat name.
// This information is used in adding suppression annotations on class level.
return record;
}
ClassFieldRecord record = new ClassFieldRecord(path, clazz);
members.forEach(
bodyDeclaration ->
bodyDeclaration.ifFieldDeclaration(
fieldDeclaration -> {
NodeList<VariableDeclarator> vars = fieldDeclaration.getVariables();
if (vars.getFirst().isEmpty()) {
// unexpected but just in case.
return;
}
record.addFieldDeclaration(fieldDeclaration);
// Collect uninitialized fields at declaration.
vars.forEach(
variableDeclarator -> {
String fieldName = variableDeclarator.getNameAsString();
if (variableDeclarator.getInitializer().isEmpty()) {
uninitializedFields.put(clazz, fieldName);
}
});
}));
// We still want to keep the information about the class even if it has no field
// declarations, so we can retrieve tha path to the file from the given class flat name.
// This information is used in adding suppression annotations on class level.
return record;
};
}

Expand Down Expand Up @@ -194,12 +213,10 @@ public OnField getLocationOnField(String clazz, String field) {
}

/**
* Creates a {@link edu.ucr.cs.riple.injector.location.OnClass} instance targeting the passed
* classes flat name.
* Creates a {@link OnClass} instance targeting the passed classes flat name.
*
* @param clazz Enclosing class of the field.
* @return {@link edu.ucr.cs.riple.injector.location.OnClass} instance targeting the passed
* classes flat name.
* @return {@link OnClass} instance targeting the passed classes flat name.
*/
public OnClass getLocationOnClass(String clazz) {
ClassFieldRecord candidate =
Expand Down

0 comments on commit 2229914

Please sign in to comment.