1 /* 2 * Copyright (C) 2018 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.apifinder; 18 19 import static com.google.errorprone.BugPattern.SeverityLevel.SUGGESTION; 20 21 import com.google.auto.service.AutoService; 22 import com.google.errorprone.BugPattern; 23 import com.google.errorprone.VisitorState; 24 import com.google.errorprone.bugpatterns.BugChecker; 25 import com.google.errorprone.bugpatterns.BugChecker.MethodInvocationTreeMatcher; 26 import com.google.errorprone.bugpatterns.BugChecker.NewClassTreeMatcher; 27 import com.google.errorprone.matchers.Description; 28 import com.google.errorprone.util.ASTHelpers; 29 import com.sun.source.tree.MethodInvocationTree; 30 import com.sun.source.tree.NewClassTree; 31 import com.sun.tools.javac.code.Symbol; 32 import com.sun.tools.javac.code.Symbol.MethodSymbol; 33 import com.sun.tools.javac.code.Symbol.VarSymbol; 34 import java.util.ArrayList; 35 import java.util.List; 36 import javax.lang.model.element.ElementKind; 37 import javax.lang.model.element.Modifier; 38 39 /** Bug checker to detect method or field used by a mainline module */ 40 @AutoService(BugChecker.class) 41 @BugPattern( 42 name = "JavaApiUsedByMainlineModule", 43 summary = "Any public method used by a mainline module.", 44 severity = SUGGESTION) 45 public final class JavaApiUsedByMainlineModule extends BugChecker 46 implements MethodInvocationTreeMatcher, NewClassTreeMatcher { 47 48 /* 49 * Checks if a method or class is private. 50 * A method is considered as private method when any of the following condition met. 51 * 1. Method is defined as private. 52 * 2. Method's class is defined as private. 53 * 3. Method's ancestor classes is defined as private. 54 */ isPrivate(Symbol sym)55 private boolean isPrivate(Symbol sym) { 56 Symbol tmpSym = sym; 57 while (tmpSym != null) { 58 if (!tmpSym.getModifiers().contains(Modifier.PUBLIC)) { 59 return true; 60 } 61 tmpSym = ASTHelpers.enclosingClass(tmpSym); 62 } 63 return false; 64 } 65 66 /* 67 * Constructs parameters. Only return parameter type. 68 * For example 69 * (int, boolean, java.lang.String) 70 */ constructParameters(Symbol sym)71 private String constructParameters(Symbol sym) { 72 List<VarSymbol> paramsList = ((MethodSymbol) sym).getParameters(); 73 List<StringBuilder> stringParamsList = new ArrayList(); 74 for (VarSymbol paramSymbol : paramsList) { 75 StringBuilder tmpParam = new StringBuilder(paramSymbol.asType().toString()); 76 77 // Removes "<*>" in parameter type. 78 if (tmpParam.indexOf("<") != -1) { 79 tmpParam = tmpParam.replace( 80 tmpParam.indexOf("<"), tmpParam.lastIndexOf(">") + 1, ""); 81 } 82 83 stringParamsList.add(tmpParam); 84 } 85 return "(" + String.join(", ", stringParamsList) + ")"; 86 } 87 88 @Override matchMethodInvocation(MethodInvocationTree tree, VisitorState state)89 public Description matchMethodInvocation(MethodInvocationTree tree, VisitorState state) { 90 Symbol sym = ASTHelpers.getSymbol(tree); 91 92 // Exclude private function. 93 if (isPrivate(sym)) { 94 return Description.NO_MATCH; 95 } 96 97 // Constructs method name formatted as superClassName.className.methodName, 98 // using supperClassName.className.className for constructor 99 String methodName = sym.name.toString(); 100 List<String> nameList = new ArrayList(); 101 if (sym.getKind() == ElementKind.CONSTRUCTOR) { 102 Symbol classSymbol = ASTHelpers.enclosingClass(sym); 103 while (classSymbol != null) { 104 nameList.add(0, classSymbol.name.toString()); 105 classSymbol = ASTHelpers.enclosingClass(classSymbol); 106 } 107 methodName = String.join(".", nameList); 108 } 109 110 String params = constructParameters(sym); 111 112 return buildDescription(tree) 113 .setMessage( 114 String.format("%s.%s%s", ASTHelpers.enclosingClass(sym), methodName, params)) 115 .build(); 116 } 117 118 @Override matchNewClass(NewClassTree tree, VisitorState state)119 public Description matchNewClass(NewClassTree tree, VisitorState state) { 120 Symbol sym = ASTHelpers.getSymbol(tree); 121 122 // Excludes private class. 123 if (isPrivate(sym)) { 124 return Description.NO_MATCH; 125 } 126 127 String params = constructParameters(sym); 128 129 // Constructs constructor name. 130 Symbol tmpSymbol = ASTHelpers.enclosingClass(sym); 131 List<String> nameList = new ArrayList(); 132 while (tmpSymbol != null) { 133 nameList.add(0, tmpSymbol.name.toString()); 134 tmpSymbol = ASTHelpers.enclosingClass(tmpSymbol); 135 } 136 String constructorName = String.join(".", nameList); 137 138 return buildDescription(tree) 139 .setMessage( 140 String.format( 141 "%s.%s%s", ASTHelpers.enclosingClass(sym), constructorName, params)) 142 .build(); 143 } 144 } 145 146