diff --git a/annotator-core/src/main/java/edu/ucr/cs/riple/core/registries/field/FieldRegistry.java b/annotator-core/src/main/java/edu/ucr/cs/riple/core/registries/field/FieldRegistry.java index e5d1a869e..73835f54a 100644 --- a/annotator-core/src/main/java/edu/ucr/cs/riple/core/registries/field/FieldRegistry.java +++ b/annotator-core/src/main/java/edu/ucr/cs/riple/core/registries/field/FieldRegistry.java @@ -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; @@ -92,46 +93,64 @@ protected void setup() { @Override protected Builder 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> members; - try { - members = Helper.getTypeDeclarationMembersByFlatName(tree, clazz); - } catch (TargetClassNotFound notFound) { - System.err.println(notFound.getMessage()); - return null; + return new Builder<>() { + Pair 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> 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 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 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; }; } @@ -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 =