From aea03f60b3ec7143ce9267c648e6750ee9f74255 Mon Sep 17 00:00:00 2001 From: NiharikaJamble Date: Thu, 1 Aug 2024 11:56:00 -0400 Subject: [PATCH] Update TargetMemberFinderVisitor.java --- .../specimin/TargetMemberFinderVisitor.java | 544 ++++++++---------- 1 file changed, 237 insertions(+), 307 deletions(-) diff --git a/src/main/java/org/checkerframework/specimin/TargetMemberFinderVisitor.java b/src/main/java/org/checkerframework/specimin/TargetMemberFinderVisitor.java index 6f08f348..b13a6cc1 100644 --- a/src/main/java/org/checkerframework/specimin/TargetMemberFinderVisitor.java +++ b/src/main/java/org/checkerframework/specimin/TargetMemberFinderVisitor.java @@ -1,7 +1,6 @@ package org.checkerframework.specimin; - +import com.github.javaparser.ast.ImportDeclaration; import com.github.javaparser.ast.Node; -import com.github.javaparser.ast.PackageDeclaration; import com.github.javaparser.ast.body.ClassOrInterfaceDeclaration; import com.github.javaparser.ast.body.ConstructorDeclaration; import com.github.javaparser.ast.body.EnumConstantDeclaration; @@ -9,8 +8,8 @@ import com.github.javaparser.ast.body.FieldDeclaration; import com.github.javaparser.ast.body.MethodDeclaration; import com.github.javaparser.ast.body.Parameter; +import com.github.javaparser.ast.body.TypeDeclaration; import com.github.javaparser.ast.body.VariableDeclarator; -import com.github.javaparser.ast.expr.AssignExpr; import com.github.javaparser.ast.expr.Expression; import com.github.javaparser.ast.expr.FieldAccessExpr; import com.github.javaparser.ast.expr.LambdaExpr; @@ -25,19 +24,17 @@ 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.MethodUsage; import com.github.javaparser.resolution.UnsolvedSymbolException; import com.github.javaparser.resolution.declarations.ResolvedConstructorDeclaration; -import com.github.javaparser.resolution.declarations.ResolvedEnumConstantDeclaration; import com.github.javaparser.resolution.declarations.ResolvedFieldDeclaration; import com.github.javaparser.resolution.declarations.ResolvedMethodDeclaration; -import com.github.javaparser.resolution.declarations.ResolvedParameterDeclaration; import com.github.javaparser.resolution.declarations.ResolvedTypeParameterDeclaration; import com.github.javaparser.resolution.declarations.ResolvedValueDeclaration; import com.github.javaparser.resolution.types.ResolvedReferenceType; import com.github.javaparser.resolution.types.ResolvedType; -import com.github.javaparser.resolution.types.ResolvedWildcard; +import com.google.common.base.Splitter; import java.util.HashMap; import java.util.HashSet; import java.util.List; @@ -45,22 +42,45 @@ import java.util.Optional; import java.util.Set; import java.util.stream.Collectors; - /** - * The main visitor for Specimin's first phase, which locates the target member(s) and compiles + * The main visitor for Specimin's first phase, which locates the target method(s) and compiles * information on what specifications they use. */ -public class TargetMemberFinderVisitor extends SpeciminStateVisitor { +public class TargetMethodFinderVisitor extends ModifierVisitor { /** * The names of the target methods. The format is * class.fully.qualified.Name#methodName(Param1Type, Param2Type, ...). All the names will have * spaces remove for ease of comparison. */ - private final Set targetMethodNames; - - /** The name of the package currently being visited. */ - private String currentPackage = ""; - + private Set targetMethodNames; + /** The names of the target fields. The format is class.fully.qualified.Name#fieldName. */ + private Set targetFieldNames; + /** + * This boolean tracks whether the element currently being visited is inside a target method. It + * is set by {@link #visit(MethodDeclaration, Void)}. + */ + private boolean insideTargetMember = false; + /** The fully-qualified name of the class currently being visited. */ + private String classFQName = ""; + /** + * The members (methods and fields) that were actually used by the targets, and therefore ought to + * have their specifications (but not bodies) preserved. The Strings in the set are the + * fully-qualified names, as returned by ResolvedMethodDeclaration#getQualifiedSignature for + * methods and FieldAccessExpr#getName for fields. + */ + private final Set usedMembers = new HashSet<>(); + /** + * Type elements (classes, interfaces, and enums) related to the methods used by the targets. + * These classes will be included in the input. + */ + private Set usedTypeElement = new HashSet<>(); + /** Set of variables declared in this current class */ + private final Set declaredNames = new HashSet<>(); + /** + * The resolved target methods. The Strings in the set are the fully-qualified names, as returned + * by ResolvedMethodDeclaration#getQualifiedSignature. + */ + private final Set targetMethods = new HashSet<>(); /** * The keys of this map are a local copy of the input list of methods. A method is removed from * this copy's key set when it is located. If the visitor has been run on all source files and the @@ -70,24 +90,22 @@ public class TargetMemberFinderVisitor extends SpeciminStateVisitor { * name/signature of a method actually is. */ private final Map> unfoundMethods; - - /** Same as the unfoundMethods set, but for fields */ - private final Map> unfoundFields = new HashMap<>(); - + /** + * This map has the name of an imported class as key and the package of that class as the value. + */ + private final Map importedClassToPackage; /** * This map connects the resolved declaration of a method to the interface that contains it, if * any. */ private final Map methodDeclarationToInterfaceType = new HashMap<>(); - /** * This map connects the fully-qualified names of non-primary classes with the fully-qualified * names of their corresponding primary classes. A primary class is a class that has the same name * as the Java file where the class is declared. */ Map nonPrimaryClassesToPrimaryClass; - /** * JavaParser is not perfect. Sometimes it can't solve resolved method calls if they have * complicated type variables or if the receiver is the parameter of a lambda expression. We keep @@ -97,27 +115,34 @@ public class TargetMemberFinderVisitor extends SpeciminStateVisitor { * appended. Anything that matches the latter will later be preserved. */ private final Set resolvedYetStuckMethodCall = new HashSet<>(); - /** * Create a new target method finding visitor. * - * @param previous the previous Specimin visitor + * @param methodNames the names of the target methods, the format + * class.fully.qualified.Name#methodName(Param1Type, Param2Type, ...) + * @param fieldNames the names of the target fields, the format is + * class.fully.qualified.Name#fieldName * @param nonPrimaryClassesToPrimaryClass map connecting non-primary classes with their * corresponding primary classes + * @param usedTypeElement set of type elements used by target methods. */ - public TargetMemberFinderVisitor( - SpeciminStateVisitor previous, Map nonPrimaryClassesToPrimaryClass) { - super(previous); + public TargetMethodFinderVisitor( + List methodNames, + List fieldNames, + Map nonPrimaryClassesToPrimaryClass, + Set usedTypeElement) { targetMethodNames = new HashSet<>(); - for (String methodSignature : targetMethods) { + for (String methodSignature : methodNames) { this.targetMethodNames.add(methodSignature.replaceAll("\\s", "")); } - unfoundMethods = new HashMap<>(targetMethods.size()); + targetFieldNames = new HashSet<>(); + targetFieldNames.addAll(fieldNames); + unfoundMethods = new HashMap<>(methodNames.size()); targetMethodNames.forEach(m -> unfoundMethods.put(m, new HashSet<>())); - targetFields.forEach(f -> unfoundFields.put(f, new HashSet<>())); + importedClassToPackage = new HashMap<>(); this.nonPrimaryClassesToPrimaryClass = nonPrimaryClassesToPrimaryClass; + this.usedTypeElement = usedTypeElement; } - /** * Returns the methods that so far this visitor has not located from its target list. Usually, * this should be checked after running the visitor to ensure that it is empty. The targets are @@ -130,20 +155,34 @@ public TargetMemberFinderVisitor( public Map> getUnfoundMethods() { return unfoundMethods; } - /** - * Returns the fields that so far this visitor has not located from its target list. Usually, this - * should be checked after running the visitor to ensure that it is empty. The targets are the - * keys in the returned maps; the values are fields in the same class that were considered but - * were not the target (useful for issuing error messages). + * Get the methods that this visitor has concluded that the target method(s) use, and therefore + * ought to be retained. The Strings in the set are the fully-qualified names, as returned by + * ResolvedMethodDeclaration#getQualifiedSignature. * - * @return the fields that so far this visitor has not located from its target list, mapped to the - * candidate fields that were considered + * @return the used methods */ - public Map> getUnfoundFields() { - return unfoundFields; + public Set getUsedMembers() { + return usedMembers; + } + /** + * Get the classes of the methods and enums that the target method uses. The Strings in the set + * are the fully-qualified names. + * + * @return the used type elements. + */ + public Set getUsedTypeElement() { + return usedTypeElement; + } + /** + * Get the target methods that this visitor has encountered so far. The Strings in the set are the + * fully-qualified names, as returned by ResolvedMethodDeclaration#getQualifiedSignature. + * + * @return the target methods + */ + public Set getTargetMethods() { + return targetMethods; } - /** * Get the set of resolved yet stuck method calls. * @@ -152,7 +191,6 @@ public Map> getUnfoundFields() { public Set getResolvedYetStuckMethodCall() { return resolvedYetStuckMethodCall; } - /** * Updates the mapping of method declarations to their corresponding interface type based on a * list of methods and the interface type that contains those methods. @@ -166,7 +204,6 @@ private void updateMethodDeclarationToInterfaceType( this.methodDeclarationToInterfaceType.put(method, interfaceType); } } - /** * Updates unfoundMethods so that the appropriate elements have their set of considered methods * updated to match a method that was not a target method. @@ -176,9 +213,8 @@ private void updateMethodDeclarationToInterfaceType( private void updateUnfoundMethods(String methodAsString) { Set targetMethodsInClass = targetMethodNames.stream() - .filter(t -> t.startsWith(this.currentClassQualifiedName)) + .filter(t -> t.startsWith(this.classFQName)) .collect(Collectors.toSet()); - for (String targetMethodInClass : targetMethodsInClass) { // This check is necessary to avoid an NPE if the target method // in question has already been removed from unfoundMethods. @@ -187,83 +223,124 @@ private void updateUnfoundMethods(String methodAsString) { } } } - - /** - * Updates unfoundFields so that the appropriate elements have their set of considered fields - * updated to match a field that was not a target field. - * - * @param fieldAsString the field that wasn't a target field - */ - private void updateUnfoundFields(String fieldAsString) { - Set targetFieldsInClass = - targetFields.stream() - .filter(t -> t.startsWith(this.currentClassQualifiedName)) - .collect(Collectors.toSet()); - - for (String targetFieldInClass : targetFieldsInClass) { - // This check is necessary to avoid an NPE if the target field - // in question has already been removed from unfoundFields. - if (unfoundFields.containsKey(targetFieldInClass)) { - unfoundFields.get(targetFieldInClass).add(fieldAsString); - } - } - } - @Override - public Visitable visit(PackageDeclaration decl, Void p) { - this.currentPackage = decl.getNameAsString(); + public Node visit(ImportDeclaration decl, Void p) { + String classFullName = decl.getNameAsString(); + if (decl.isStatic()) { + classFullName = classFullName.substring(0, classFullName.lastIndexOf(".")); + } + String classSimpleName = classFullName.substring(classFullName.lastIndexOf(".") + 1); + String packageName = classFullName.replace("." + classSimpleName, ""); + importedClassToPackage.put(classSimpleName, packageName); return super.visit(decl, p); } - @Override public Visitable visit(ClassOrInterfaceDeclaration decl, Void p) { for (ClassOrInterfaceType interfaceType : decl.getImplementedTypes()) { try { updateMethodDeclarationToInterfaceType( - JavaParserUtil.classOrInterfaceTypeToResolvedReferenceType(interfaceType) - .getAllMethods(), - interfaceType); + interfaceType.resolve().getAllMethods(), interfaceType); } catch (UnsolvedSymbolException e) { continue; } } - - return super.visit(decl, p); + manageClassFQNamePreSuper(decl); + Visitable result = super.visit(decl, p); + manageClassFQNamePostSuper(decl); + return result; + } + @Override + public Visitable visit(EnumDeclaration decl, Void p) { + manageClassFQNamePreSuper(decl); + Visitable result = super.visit(decl, p); + manageClassFQNamePostSuper(decl); + return result; + } + /** + * Helper method to share code for managing the {@link #classFQName} field between + * classes/interfaces and enums. Call this method before calling super.visit(). + * + * @see #classFQName + * @see #manageClassFQNamePostSuper(TypeDeclaration) + * @param decl a class, interface, or enum declaration + */ + private void manageClassFQNamePreSuper(TypeDeclaration decl) { + if (decl.isNestedType()) { + this.classFQName += "." + decl.getName().toString(); + } else { + boolean isLocalClass = + decl.isClassOrInterfaceDeclaration() + && decl.asClassOrInterfaceDeclaration().isLocalClassDeclaration(); + if (!isLocalClass) { + if (!this.classFQName.equals("")) { + throw new UnsupportedOperationException( + "Attempted to enter an unexpected kind of class: " + + + + + + + + Expand All + + @@ -323,7 +320,7 @@ private void manageClassFQNamePreSuper(TypeDeclaration decl) { + + + decl.getFullyQualifiedName() + + " but already had a set classFQName: " + + classFQName); + } + // Should always be present. + this.classFQName = decl.getFullyQualifiedName().orElseThrow(); + } + } + } + /** + * Helper method to share code for managing the {@link #classFQName} field between + * classes/interfaces and enums. Call this method after calling super.visit(). + * + * @see #classFQName + * @see #manageClassFQNamePreSuper(TypeDeclaration) + * @param decl a class, interface, or enum declaration + */ + private void manageClassFQNamePostSuper(TypeDeclaration decl) { + if (decl.isNestedType()) { + this.classFQName = this.classFQName.substring(0, this.classFQName.lastIndexOf('.')); + } else { + this.classFQName = ""; + } } + + + + + + + Expand Down + + + @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.currentClassQualifiedName + "#" + constructorMethodAsString; - // remove spaces - methodName = methodName.replaceAll("\\s", ""); + String methodName = this.classFQName + "#" + constructorMethodAsString; + boolean oldInsideTargetMember = insideTargetMember; if (this.targetMethodNames.contains(methodName)) { + insideTargetMember = true; ResolvedConstructorDeclaration resolvedMethod = method.resolve(); targetMethods.add(resolvedMethod.getQualifiedSignature()); unfoundMethods.remove(methodName); updateUsedClassWithQualifiedClassName( resolvedMethod.getPackageName() + "." + resolvedMethod.getClassName(), - usedTypeElements, + usedTypeElement, nonPrimaryClassesToPrimaryClass); - if (modularityModel.preserveAllFieldsIfTargetIsConstructor()) { - // This cast is safe, because a constructor must be contained in a class declaration. - ClassOrInterfaceDeclaration thisClass = - (ClassOrInterfaceDeclaration) JavaParserUtil.getEnclosingClassLike(method); - for (FieldDeclaration field : thisClass.getFields()) { - for (VariableDeclarator variable : field.getVariables()) { - usedMembers.add(currentClassQualifiedName + "#" + variable.getNameAsString()); - ResolvedType fieldType = variable.resolve().getType(); - updateUsedClassBasedOnType(fieldType); - } - } - } } else { updateUnfoundMethods(methodName); } - Visitable result = super.visit(method, p); - + insideTargetMember = oldInsideTargetMember; if (method.getParentNode().isEmpty()) { return result; } @@ -273,7 +350,7 @@ public Visitable visit(ConstructorDeclaration method, Void p) { return result; } // used enums needs to have compilable constructors. - if (usedTypeElements.contains(parentNode.getFullyQualifiedName().orElseThrow())) { + if (usedTypeElement.contains(parentNode.getFullyQualifiedName().get())) { for (Parameter parameter : method.getParameters()) { updateUsedClassBasedOnType(parameter.getType().resolve()); } @@ -281,59 +358,29 @@ public Visitable visit(ConstructorDeclaration method, Void p) { } return result; } - @Override public Visitable visit(VariableDeclarator node, Void arg) { + declaredNames.add(node.getNameAsString()); if (node.getParentNode().isPresent() && node.getParentNode().get() instanceof FieldDeclaration) { - String fieldName = this.currentClassQualifiedName + "#" + node.getNameAsString(); - if (targetFields.contains(fieldName)) { - ResolvedFieldDeclaration resolvedField = - ((FieldDeclaration) node.getParentNode().get()).resolve(); - unfoundFields.remove(fieldName); - updateUsedClassWithQualifiedClassName( - resolvedField.declaringType().getQualifiedName(), - usedTypeElements, - nonPrimaryClassesToPrimaryClass); - } else { - updateUnfoundFields(fieldName); + if (targetFieldNames.contains(this.classFQName + "#" + node.getNameAsString())) { + boolean oldInsideTargetMember = insideTargetMember; + insideTargetMember = true; + Visitable result = super.visit(node, arg); + usedTypeElement.add(this.classFQName); + insideTargetMember = oldInsideTargetMember; + return result; } } return super.visit(node, arg); } - - @Override - public Visitable visit(AssignExpr node, Void p) { - if (insideTargetCtor) { - // check if the LHS is a field - Expression lhs = node.getTarget(); - if (lhs.isFieldAccessExpr()) { - FieldAccessExpr asFieldAccess = lhs.asFieldAccessExpr(); - Expression scope = asFieldAccess.getScope(); - if (scope.toString().equals("this")) { - fieldsAssignedByTargetCtors.add( - currentClassQualifiedName + "#" + asFieldAccess.getNameAsString()); - } - } else if (lhs.isNameExpr()) { - // could be a field of "this" - NameExpr asName = lhs.asNameExpr(); - ResolvedValueDeclaration resolved = asName.resolve(); - if (resolved.isField()) { - fieldsAssignedByTargetCtors.add( - currentClassQualifiedName + "#" + asName.getNameAsString()); - } - } - } - return super.visit(node, p); - } - @Override public Visitable visit(MethodDeclaration method, Void p) { boolean oldInsideTargetMember = insideTargetMember; + String methodDeclAsString = method.getDeclarationAsString(false, false, false); // TODO: test this with annotations - String methodWithoutReturnAndAnnos = - JavaParserUtil.removeMethodReturnTypeAndAnnotations(method); - String methodName = this.currentClassQualifiedName + "#" + methodWithoutReturnAndAnnos; + String methodWithoutReturnAndAnnos = removeMethodReturnTypeAndAnnotations(methodDeclAsString); + String methodName = this.classFQName + "#" + methodWithoutReturnAndAnnos; // this method belongs to an anonymous class inside the target method if (insideTargetMember) { Node parentNode = method.getParentNode().get(); @@ -345,7 +392,7 @@ public Visitable visit(MethodDeclaration method, Void p) { String methodClass = resolved.getClassName(); usedMembers.add(methodPackage + "." + methodClass + "." + method.getNameAsString() + "()"); updateUsedClassWithQualifiedClassName( - methodPackage + "." + methodClass, usedTypeElements, nonPrimaryClassesToPrimaryClass); + methodPackage + "." + methodClass, usedTypeElement, nonPrimaryClassesToPrimaryClass); } } String methodWithoutAnySpace = methodName.replaceAll("\\s", ""); @@ -354,9 +401,8 @@ public Visitable visit(MethodDeclaration method, Void p) { updateUsedClassesForInterface(resolvedMethod); updateUsedClassWithQualifiedClassName( resolvedMethod.getPackageName() + "." + resolvedMethod.getClassName(), - usedTypeElements, + usedTypeElement, nonPrimaryClassesToPrimaryClass); - insideTargetMember = true; targetMethods.add(resolvedMethod.getQualifiedSignature()); // make sure that differences in spacing does not interfere with the result @@ -379,23 +425,14 @@ public Visitable visit(MethodDeclaration method, Void p) { // the type variable must have been declared in one of the containing scopes, // and UnsolvedSymbolVisitor should already guarantee that the variable will // be included in one of the classes that Specimin outputs. - } catch (UnsolvedSymbolException e) { - throw new RuntimeException( - "failed to solve the return type (" - + returnType - + ") of " - + methodWithoutReturnAndAnnos, - e); } } else { updateUnfoundMethods(methodName); } - Visitable result = super.visit(method, p); insideTargetMember = oldInsideTargetMember; return result; } - @Override public Visitable visit(Parameter para, Void p) { if (insideTargetMember) { @@ -421,12 +458,11 @@ public Visitable visit(Parameter para, Void p) { throw new RuntimeException("cannot solve: " + para, e); } } - if (paramType.isReferenceType()) { String paraTypeFullName = - paramType.asReferenceType().getTypeDeclaration().orElseThrow().getQualifiedName(); + paramType.asReferenceType().getTypeDeclaration().get().getQualifiedName(); updateUsedClassWithQualifiedClassName( - paraTypeFullName, usedTypeElements, nonPrimaryClassesToPrimaryClass); + paraTypeFullName, usedTypeElement, nonPrimaryClassesToPrimaryClass); for (ResolvedType typeParameterValue : paramType.asReferenceType().typeParametersValues()) { String typeParameterValueName = typeParameterValue.describe(); @@ -436,15 +472,13 @@ public Visitable visit(Parameter para, Void p) { typeParameterValueName.substring(0, typeParameterValueName.indexOf("<")); } updateUsedClassWithQualifiedClassName( - typeParameterValueName, usedTypeElements, nonPrimaryClassesToPrimaryClass); + typeParameterValueName, usedTypeElement, nonPrimaryClassesToPrimaryClass); } } } } - return super.visit(para, p); } - /** * Returns true iff we can prove that the parameter is a lambda parameter. This method should only * be called on parameters that are not of unknown type (which are definitely lambda params). @@ -455,7 +489,6 @@ public Visitable visit(Parameter para, Void p) { private boolean isLambdaParam(Parameter para) { return para.getParentNode().orElseThrow() instanceof LambdaExpr; } - @Override public Visitable visit(MethodReferenceExpr ref, Void p) { if (insideTargetMember) { @@ -464,7 +497,6 @@ public Visitable visit(MethodReferenceExpr ref, Void p) { } return super.visit(ref, p); } - @Override public Visitable visit(MethodCallExpr call, Void p) { if (insideTargetMember) { @@ -489,45 +521,7 @@ public Visitable visit(MethodCallExpr call, Void p) { // leading to a RuntimeException. // Note: this preservation is safe because we are not having an UnsolvedSymbolException. // Only unsolved symbols can make the output failed to compile. - if (call.hasScope()) { - Expression scope = call.getScope().orElseThrow(); - String scopeAsString = scope.toString(); - if (scopeAsString.equals("this") || scopeAsString.equals("super")) { - // In the "super" case, it would be better to add the name of an - // extended or implemented class/interface. However, there are two complications: - // 1) we currently don't track the list of classes/interfaces that the current class - // extends and/or implements in this visitor and 2) even if we did track that, there - // is no way for us to know which of those classes/interfaces the method belongs to. - // TODO: write a test for the "super" case and then figure out a better way to handle - // it. - resolvedYetStuckMethodCall.add( - this.currentClassQualifiedName + "." + call.getNameAsString()); - } else { - // Use the scope instead. First, check if it's resolvable. If it is, great - - // just use that. If not, then we need to use some heuristics as fallbacks. - try { - ResolvedType scopeType = scope.calculateResolvedType(); - resolvedYetStuckMethodCall.add(scopeType.describe() + "." + call.getNameAsString()); - usedTypeElements.add(scopeType.describe()); - } catch (Exception e1) { - // There are two fallback cases: the scope is an FQN (e.g., in - // a call to a fully-qualified static method) or the scope is a simple name. - // In the simple name case, append the current package to the front, since - // if it had been imported we wouldn't be in this situation. - if (UnsolvedSymbolVisitor.isAClassPath(scopeAsString)) { - resolvedYetStuckMethodCall.add(scopeAsString + "." + call.getNameAsString()); - usedTypeElements.add(scopeAsString); - } else { - resolvedYetStuckMethodCall.add( - getCurrentPackage() + "." + scopeAsString + "." + call.getNameAsString()); - usedTypeElements.add(getCurrentPackage() + "." + scopeAsString); - } - } - } - } else { - resolvedYetStuckMethodCall.add( - this.currentClassQualifiedName + "." + call.getNameAsString()); - } + resolvedYetStuckMethodCall.add(this.classFQName + "." + call.getNameAsString()); return super.visit(call, p); } preserveMethodDecl(decl); @@ -537,24 +531,11 @@ public Visitable visit(MethodCallExpr call, Void p) { Expression arg = call.getArgument(i); if (arg.isLambdaExpr()) { updateUsedClassBasedOnType(decl.getParam(i).getType()); - // We should mark the abstract method for preservation as well - if (decl.getParam(i).getType().isReferenceType()) { - ResolvedReferenceType functionalInterface = - decl.getParam(i).getType().asReferenceType(); - for (MethodUsage method : functionalInterface.getDeclaredMethods()) { - if (method.getDeclaration().isAbstract()) { - preserveMethodDecl(method.getDeclaration()); - // Only one abstract method per functional interface - break; - } - } - } } } } return super.visit(call, p); } - /** * Helper method for preserving a used method. This code is called for both method call * expressions and method refs. @@ -565,7 +546,7 @@ private void preserveMethodDecl(ResolvedMethodDeclaration decl) { usedMembers.add(decl.getQualifiedSignature()); updateUsedClassWithQualifiedClassName( decl.getPackageName() + "." + decl.getClassName(), - usedTypeElements, + usedTypeElement, nonPrimaryClassesToPrimaryClass); try { ResolvedType methodReturnType = decl.getReturnType(); @@ -579,32 +560,14 @@ private void preserveMethodDecl(ResolvedMethodDeclaration decl) { catch (UnsolvedSymbolException e) { return; } - - for (int i = 0; i < decl.getNumberOfParams(); ++i) { - // Why is there no getParams() method?? - ResolvedParameterDeclaration p = decl.getParam(i); - ResolvedType pType = p.getType(); - updateUsedClassBasedOnType(pType); - } } - - /** - * Gets the package name of the current class. - * - * @return the current package name - */ - private String getCurrentPackage() { - return currentPackage; - } - @Override public Visitable visit(ClassOrInterfaceType type, Void p) { if (!insideTargetMember) { return super.visit(type, p); } try { - ResolvedReferenceType typeResolved = - JavaParserUtil.classOrInterfaceTypeToResolvedReferenceType(type); + ResolvedReferenceType typeResolved = type.resolve(); updateUsedClassBasedOnType(typeResolved); } // if the type has a fully-qualified form, JavaParser also consider other components rather than @@ -616,30 +579,18 @@ public Visitable visit(ClassOrInterfaceType type, Void p) { } return super.visit(type, p); } - @Override public Visitable visit(ObjectCreationExpr newExpr, Void p) { if (insideTargetMember) { - try { - ResolvedConstructorDeclaration resolved = newExpr.resolve(); - usedMembers.add(resolved.getQualifiedSignature()); - updateUsedClassWithQualifiedClassName( - resolved.getPackageName() + "." + resolved.getClassName(), - usedTypeElements, - nonPrimaryClassesToPrimaryClass); - for (int i = 0; i < resolved.getNumberOfParams(); ++i) { - // Why is there no getParams() method?? - ResolvedParameterDeclaration param = resolved.getParam(i); - ResolvedType pType = param.getType(); - updateUsedClassBasedOnType(pType); - } - } catch (UnsolvedSymbolException e) { - throw new RuntimeException("trying to resolve : " + newExpr, e); - } + ResolvedConstructorDeclaration resolved = newExpr.resolve(); + usedMembers.add(resolved.getQualifiedSignature()); + updateUsedClassWithQualifiedClassName( + resolved.getPackageName() + "." + resolved.getClassName(), + usedTypeElement, + nonPrimaryClassesToPrimaryClass); } return super.visit(newExpr, p); } - @Override public Visitable visit(ExplicitConstructorInvocationStmt expr, Void p) { if (insideTargetMember) { @@ -647,35 +598,28 @@ public Visitable visit(ExplicitConstructorInvocationStmt expr, Void p) { usedMembers.add(resolved.getQualifiedSignature()); updateUsedClassWithQualifiedClassName( resolved.getPackageName() + "." + resolved.getClassName(), - usedTypeElements, + usedTypeElement, nonPrimaryClassesToPrimaryClass); } return super.visit(expr, p); } - @Override public Visitable visit(EnumConstantDeclaration enumConstantDeclaration, Void p) { - Node parentNode = enumConstantDeclaration.getParentNode().orElseThrow(); - + Node parentNode = enumConstantDeclaration.getParentNode().get(); if (parentNode instanceof EnumDeclaration) { - if (usedTypeElements.contains( - ((EnumDeclaration) parentNode) - .asEnumDeclaration() - .getFullyQualifiedName() - .orElseThrow())) { + if (usedTypeElement.contains( + ((EnumDeclaration) parentNode).asEnumDeclaration().getFullyQualifiedName().get())) { boolean oldInsideTargetMember = insideTargetMember; // used enum constant are not strictly target methods, but we need to make sure the symbols // inside them are preserved. insideTargetMember = true; Visitable result = super.visit(enumConstantDeclaration, p); insideTargetMember = oldInsideTargetMember; - return result; } } return super.visit(enumConstantDeclaration, p); } - @Override public Visitable visit(FieldAccessExpr expr, Void p) { if (insideTargetMember) { @@ -689,21 +633,15 @@ public Visitable visit(FieldAccessExpr expr, Void p) { fullNameOfClass = expr.resolve().asField().declaringType().getQualifiedName(); usedMembers.add(fullNameOfClass + "#" + expr.getName().asString()); updateUsedClassWithQualifiedClassName( - fullNameOfClass, usedTypeElements, nonPrimaryClassesToPrimaryClass); + fullNameOfClass, usedTypeElement, nonPrimaryClassesToPrimaryClass); ResolvedType exprResolvedType = expr.resolve().getType(); updateUsedClassBasedOnType(exprResolvedType); } catch (UnsolvedSymbolException | UnsupportedOperationException e) { // when the type is a primitive array, we will have an UnsupportedOperationException if (e instanceof UnsupportedOperationException) { - Expression scope = expr.getScope(); - if (scope.isNameExpr()) { - updateUsedElementWithPotentialFieldNameExpr(scope.asNameExpr()); - } - // If the scope is not a name expression, then it must be "this" (handled elsewhere), - // "super" (handled directly below), or another field access expression (handled by - // the visitor), so there's nothing to do. + updateUsedElementWithPotentialFieldNameExpr(expr.getScope().asNameExpr()); } - // if a field is accessed in the form of a fully-qualified path, such as + // if the a field is accessed in the form of a fully-qualified path, such as // org.example.A.b, then other components in the path apart from the class name and field // name, such as org and org.example, will also be considered as FieldAccessExpr. } @@ -715,7 +653,6 @@ public Visitable visit(FieldAccessExpr expr, Void p) { } return super.visit(expr, p); } - @Override public Visitable visit(NameExpr expr, Void p) { if (insideTargetMember) { @@ -726,7 +663,6 @@ public Visitable visit(NameExpr expr, Void p) { } return super.visit(expr, p); } - /** * Updates the list of used classes based on a resolved method declaration. If the input method * originates from an interface, that interface will be added to the list of used classes. The @@ -744,12 +680,12 @@ public void updateUsedClassesForInterface(ResolvedMethodDeclaration method) { .describe() .equals(interfaceMethod.getReturnType().describe())) { if (method.getNumberOfParams() == interfaceMethod.getNumberOfParams()) { - ResolvedReferenceType resolvedInterface = - JavaParserUtil.classOrInterfaceTypeToResolvedReferenceType( - methodDeclarationToInterfaceType.get(interfaceMethod)); updateUsedClassWithQualifiedClassName( - resolvedInterface.getQualifiedName(), - usedTypeElements, + methodDeclarationToInterfaceType + .get(interfaceMethod) + .resolve() + .getQualifiedName(), + usedTypeElement, nonPrimaryClassesToPrimaryClass); usedMembers.add(interfaceMethod.getQualifiedSignature()); } @@ -761,7 +697,29 @@ public void updateUsedClassesForInterface(ResolvedMethodDeclaration method) { } } } - + /** + * Given a method declaration, this method return the declaration of that method without the + * return type and any possible annotation. + * + * @param methodDeclaration the method declaration to be used as input + * @return methodDeclaration without the return type and any possible annotation. + */ + public static String removeMethodReturnTypeAndAnnotations(String methodDeclaration) { + String methodDeclarationWithoutParen = + methodDeclaration.substring(0, methodDeclaration.indexOf("(")); + List methodParts = Splitter.onPattern(" ").splitToList(methodDeclarationWithoutParen); + String methodName = methodParts.get(methodParts.size() - 1); + String methodReturnType = methodDeclaration.substring(0, methodDeclaration.indexOf(methodName)); + String methodWithoutReturnType = methodDeclaration.replace(methodReturnType, ""); + methodParts = Splitter.onPattern(" ").splitToList(methodWithoutReturnType); + String filteredMethodDeclaration = + methodParts.stream() + .filter(part -> !part.startsWith("@")) + .map(part -> part.indexOf('@') == -1 ? part : part.substring(0, part.indexOf('@'))) + .collect(Collectors.joining(" ")); + // sometimes an extra space may occur if an annotation right after a < was removed + return filteredMethodDeclaration.replace("< ", "<"); + } /** * Resolves unionType parameters one by one and adds them in the usedClass set. * @@ -773,7 +731,6 @@ private void resolveUnionType(UnionType type) { updateUsedClassBasedOnType(paramType); } } - /** * Given a FieldAccessExpr, this method updates the sets of used classes and members if this field * is actually an enum constant. @@ -797,11 +754,10 @@ private boolean updateUsedClassAndMemberForEnumConstant(FieldAccessExpr fieldAcc } String classFullName = resolved.asEnumConstant().getType().describe(); updateUsedClassWithQualifiedClassName( - classFullName, usedTypeElements, nonPrimaryClassesToPrimaryClass); + classFullName, usedTypeElement, nonPrimaryClassesToPrimaryClass); usedMembers.add(classFullName + "." + fieldAccessExpr.getNameAsString()); return true; } - /** * Given a NameExpr instance, this method will update the used elements, classes and members if * that NameExpr is a field. @@ -821,19 +777,11 @@ public void updateUsedElementWithPotentialFieldNameExpr(NameExpr expr) { // field is declared String classFullName = exprDecl.asField().declaringType().getQualifiedName(); updateUsedClassWithQualifiedClassName( - classFullName, usedTypeElements, nonPrimaryClassesToPrimaryClass); + classFullName, usedTypeElement, nonPrimaryClassesToPrimaryClass); usedMembers.add(classFullName + "#" + expr.getNameAsString()); updateUsedClassBasedOnType(exprDecl.getType()); - } else if (exprDecl instanceof ResolvedEnumConstantDeclaration) { - String enumFullName = exprDecl.asEnumConstant().getType().describe(); - updateUsedClassWithQualifiedClassName( - enumFullName, usedTypeElements, nonPrimaryClassesToPrimaryClass); - // "." and not "#" because enum constants are not fields - usedMembers.add(enumFullName + "." + expr.getNameAsString()); - updateUsedClassBasedOnType(exprDecl.getType()); } } - /** * Updates the list of used type elements with the given qualified type name and its corresponding * primary type and enclosing type. This includes cases such as classes not sharing the same name @@ -849,7 +797,6 @@ public static void updateUsedClassWithQualifiedClassName( String qualifiedClassName, Set usedTypeElement, Map nonPrimaryClassesToPrimaryClass) { - // in case of type variables if (!qualifiedClassName.contains(".")) { return; @@ -866,7 +813,6 @@ public static void updateUsedClassWithQualifiedClassName( usedTypeElement, nonPrimaryClassesToPrimaryClass); } - String potentialOuterClass = qualifiedClassName.substring(0, qualifiedClassName.lastIndexOf(".")); if (UnsolvedSymbolVisitor.isAClassPath(potentialOuterClass)) { @@ -874,12 +820,9 @@ public static void updateUsedClassWithQualifiedClassName( potentialOuterClass, usedTypeElement, nonPrimaryClassesToPrimaryClass); } } - /** * Updates the list of used classes based on the resolved type of a used element, where a element - * can be a method, a field, a variable, or a parameter. Also updates the set of used classes - * based on component types, wildcard bounds, etc., as needed: any type that is used in the type - * will be included. + * can be a method, a field, a variable, or a parameter. * * @param type The resolved type of the used element. */ @@ -890,37 +833,24 @@ public void updateUsedClassBasedOnType(ResolvedType type) { ResolvedTypeParameterDeclaration asTypeParameter = type.asTypeParameter(); for (ResolvedTypeParameterDeclaration.Bound bound : asTypeParameter.getBounds()) { updateUsedClassWithQualifiedClassName( - bound.getType().describe(), usedTypeElements, nonPrimaryClassesToPrimaryClass); + bound.getType().describe(), usedTypeElement, nonPrimaryClassesToPrimaryClass); } return; - } else if (type.isArray()) { - ResolvedType componentType = type.asArrayType().getComponentType(); - updateUsedClassBasedOnType(componentType); - return; } updateUsedClassWithQualifiedClassName( - type.describe(), usedTypeElements, nonPrimaryClassesToPrimaryClass); + type.describe(), usedTypeElement, nonPrimaryClassesToPrimaryClass); if (!type.isReferenceType()) { return; } ResolvedReferenceType typeAsReference = type.asReferenceType(); List typeParameters = typeAsReference.typeParametersValues(); for (ResolvedType typePara : typeParameters) { - if (typePara.isPrimitive() || typePara.isTypeVariable()) { - // Nothing to do, since these are already in-scope. - continue; - } - if (typePara.isWildcard()) { - ResolvedWildcard asWildcard = typePara.asWildcard(); - // Recurse into the bound, if one exists. - if (asWildcard.isBounded()) { - updateUsedClassBasedOnType(asWildcard.getBoundedType()); - } + if (typePara.isPrimitive() || typePara.isTypeVariable() || typePara.isWildcard()) { continue; } updateUsedClassWithQualifiedClassName( typePara.asReferenceType().getQualifiedName(), - usedTypeElements, + usedTypeElement, nonPrimaryClassesToPrimaryClass); } }